From f658bfbb402943271c41213dd57ef88c84f8445e Mon Sep 17 00:00:00 2001 From: James Gregory Date: Tue, 31 Dec 2013 11:38:48 +1100 Subject: [PATCH] Use backtick code blocks for syntax highlighting --- 01-02.md | 70 +-- 01-03.md | 188 +++---- 01-04.md | 58 +- 01-05.md | 194 +++---- 01-06.md | 100 ++-- 02-01.md | 38 +- 03-02.md | 860 ++++++++++++++--------------- 03-05.md | 126 ++--- 03-06.md | 132 ++--- 03-07.md | 1196 ++++++++++++++++++++-------------------- 03-08.md | 118 ++-- 03-09.md | 188 ++++--- 03-10.md | 22 +- 04-02.md | 72 ++- 04-03.md | 62 ++- 04-04.md | 50 +- 04-05.md | 44 +- 04-07.md | 44 +- 04-09.md | 96 ++-- 05-03.md | 306 +++++------ 06-01.md | 10 +- 06-02.md | 60 +- 07-02.md | 12 +- 07-03.md | 170 +++--- 07-04.md | 242 ++++---- 07-05.md | 98 ++-- 08-02.md | 6 +- 08-03.md | 284 +++++----- 08-04.md | 294 +++++----- 08-05.md | 266 ++++----- 09-01.md | 76 +-- 09-02.md | 40 +- 09-03.md | 184 +++---- 09-04.md | 258 ++++----- 09-05.md | 57 +- 09-06.md | 154 +++--- 09-07.md | 60 +- 10-02.md | 100 ++-- 10-03.md | 140 ++--- 10-04.md | 164 +++--- 11-01.md | 12 +- 11-02.md | 40 +- 11-03.md | 70 +-- 11-04.md | 35 +- 11-06.md | 26 +- 11-07.md | 40 +- 11-08.md | 84 +-- 12-01.md | 28 +- 12-02.md | 66 ++- 12-03.md | 70 ++- 12-04.md | 105 ++-- 13-01.md | 20 +- 13-02.md | 54 +- 13-03.md | 42 +- 13-04.md | 32 +- 14-04.md | 256 ++++----- 14-05.md | 318 +++++------ 14-06.md | 290 +++++----- 15-02.md | 134 ++--- 15-03.md | 166 +++--- 15-04.md | 324 +++++------ 16-01.md | 146 ++--- 16-02.md | 280 +++++----- 16-03.md | 183 +++---- 16-04.md | 6 +- 16-05.md | 358 ++++++------ 16-07.md | 210 +++---- 16-08.md | 96 ++-- 17-02.md | 446 +++++++-------- 17-04.md | 290 +++++----- 17-05.md | 158 +++--- 17-07.md | 492 ++++++++--------- 18-03.md | 928 +++++++++++++++---------------- 18-04.md | 244 +++++---- 18-05.md | 8 +- 19-02.md | 26 +- 19-03.md | 8 +- 20-02.md | 134 ++--- 20-03.md | 24 +- 20-04.md | 12 +- 21-01.md | 22 +- 21-02.md | 108 ++-- 21-03.md | 54 +- 21-04.md | 134 ++--- 21-05.md | 132 ++--- 22-01.md | 70 +-- 22-02.md | 88 +-- 22-03.md | 104 ++-- 23-03.md | 1104 ++++++++++++++++++------------------- 24-02.md | 504 ++++++++--------- 24-03.md | 11 +- 25-02.md | 460 ++++++++-------- 25-04.md | 164 +++--- 25-05.md | 436 +++++++-------- 26-02.md | 586 ++++++++++---------- 26-03.md | 636 ++++++++++----------- 27-02.md | 414 +++++++------- 27-03.md | 628 ++++++++++----------- 27-05.md | 368 ++++++------- 28-02.md | 472 ++++++++-------- 28-04.md | 324 +++++------ 28-05.md | 208 +++---- 29-01.md | 258 ++++----- 29-02.md | 230 ++++---- 29-04.md | 502 ++++++++--------- 29-05.md | 164 +++--- 29-06.md | 18 +- 30-02.md | 746 ++++++++++++------------- 30-05.md | 838 ++++++++++++++-------------- 31-03.md | 650 +++++++++++----------- 31-05.md | 520 +++++++++--------- 32-02.md | 412 +++++++------- 32-03.md | 361 ++++++------ 33-04.md | 320 +++++------ 34-03.md | 470 ++++++++-------- 35-03.md | 344 ++++++------ 35-04.md | 142 ++--- 35-07.md | 720 ++++++++++++------------ 36-01.md | 56 +- 36-03.md | 434 +++++++-------- 36-04.md | 132 ++--- 37-01.md | 672 +++++++++++------------ 38-02.md | 468 ++++++++-------- 38-03.md | 228 ++++---- 39-02.md | 70 +-- 39-03.md | 220 ++++---- 39-04.md | 158 +++--- 39-05.md | 326 +++++------ 40-02.md | 514 ++++++++--------- 40-03.md | 264 ++++----- 40-04.md | 94 ++-- 41-01.md | 72 +-- 41-02.md | 204 +++---- 41-03.md | 170 +++--- 41-04.md | 594 ++++++++++---------- 42-02.md | 264 ++++----- 42-03.md | 286 +++++----- 42-04.md | 200 +++---- 42-05.md | 510 ++++++++--------- 43-03.md | 992 ++++++++++++++++----------------- 44-02.md | 524 +++++++++--------- 44-03.md | 598 ++++++++++---------- 44-04.md | 28 +- 45-03.md | 470 ++++++++-------- 45-04.md | 54 +- 45-05.md | 142 ++--- 46-02.md | 998 ++++++++++++++++----------------- 47-02.md | 160 +++--- 47-03.md | 180 +++--- 47-04.md | 176 +++--- 47-05.md | 248 ++++----- 47-07.md | 264 ++++----- 48-01.md | 84 +-- 48-02.md | 340 ++++++------ 48-04.md | 338 ++++++------ 48-05.md | 226 ++++---- 49-01.md | 258 ++++----- 49-02.md | 308 +++++------ 49-03.md | 160 +++--- 49-04.md | 456 ++++++++-------- 49-05.md | 92 ++-- 50-03.md | 306 +++++------ 50-04.md | 186 +++---- 50-05.md | 136 ++--- 50-06.md | 236 ++++---- 51-03.md | 314 +++++------ 51-04.md | 174 +++--- 51-05.md | 116 ++-- 51-06.md | 166 +++--- 52-02.md | 224 ++++---- 52-03.md | 274 +++++----- 52-04.md | 182 ++++--- 52-05.md | 236 ++++---- 52-06.md | 340 ++++++------ 53-02.md | 712 ++++++++++++------------ 53-04.md | 146 ++--- 54-02.md | 1606 +++++++++++++++++++++++++++--------------------------- 54-04.md | 250 ++++----- 55-01.md | 12 +- 55-02.md | 148 ++--- 55-03.md | 44 +- 55-04.md | 314 +++++------ 56-02.md | 4 +- 56-03.md | 592 ++++++++++---------- 57-02.md | 126 ++--- 57-03.md | 580 ++++++++++---------- 58-02.md | 140 ++--- 58-03.md | 14 +- 58-04.md | 64 ++- 58-05.md | 81 +-- 59-03.md | 42 +- 59-04.md | 202 +++---- 59-05.md | 134 ++--- 60-03.md | 460 ++++++++-------- 61-04.md | 60 +- 62-02.md | 880 +++++++++++++++--------------- 63-02.md | 72 ++- 63-03.md | 180 +++--- 63-04.md | 82 +-- 65-02.md | 56 +- 65-03.md | 682 +++++++++++------------ 67-03.md | 792 +++++++++++++-------------- 69-04.md | 196 +++---- 203 files changed, 25980 insertions(+), 25235 deletions(-) diff --git a/01-02.md b/01-02.md index 00c4f2c..afeee9f 100644 --- a/01-02.md +++ b/01-02.md @@ -82,46 +82,48 @@ It's *slow*. **LISTING 1.1 L1-1.C** - /* - * Program to calculate the 16-bit checksum of all bytes in the - * specified file. Obtains the bytes one at a time via read(), - * letting DOS perform all data buffering. - */ - #include - #include +```c +/* +* Program to calculate the 16-bit checksum of all bytes in the +* specified file. Obtains the bytes one at a time via read(), +* letting DOS perform all data buffering. +*/ +#include +#include - main(int argc, char *argv[]) { - int Handle; - unsigned char Byte; - unsigned int Checksum; - int ReadLength; +main(int argc, char *argv[]) { + int Handle; + unsigned char Byte; + unsigned int Checksum; + int ReadLength; - if ( argc != 2 ) { - 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]); - exit(1); - } + if ( argc != 2 ) { + 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]); + exit(1); + } - /* Initialize the checksum accumulator */ - Checksum = 0; + /* Initialize the checksum accumulator */ + Checksum = 0; - /* Add each byte in turn into the checksum accumulator */ - while ( (ReadLength = read(Handle, &Byte, sizeof(Byte))) > 0 ) { - Checksum += (unsigned int) Byte; - } - if ( ReadLength == -1 ) { - printf("Error reading file %s\n", argv[1]); - exit(1); - } + /* Add each byte in turn into the checksum accumulator */ + while ( (ReadLength = read(Handle, &Byte, sizeof(Byte))) > 0 ) { + Checksum += (unsigned int) Byte; + } + if ( ReadLength == -1 ) { + printf("Error reading file %s\n", argv[1]); + exit(1); + } - /* Report the result */ - printf("The checksum is: %u\n", Checksum); - exit(0); - } + /* Report the result */ + printf("The checksum is: %u\n", Checksum); + exit(0); +} +``` Table 1.1 shows the time taken for Listing 1.1 to generate a checksum of the WordPerfect version 4.2 thesaurus file, TH.WP (362,293 bytes in diff --git a/01-03.md b/01-03.md index 643bdf3..122e17c 100644 --- a/01-03.md +++ b/01-03.md @@ -30,105 +30,109 @@ disk caching turned off. **LISTING 1.2 L1-2.C** - /* - * Program to calculate the 16-bit checksum of the stream of bytes - * from the specified file. Obtains the bytes one at a time in - * assembler, via direct calls to DOS. - */ +```c +/* +* Program to calculate the 16-bit checksum of the stream of bytes +* from the specified file. Obtains the bytes one at a time in +* assembler, via direct calls to DOS. +*/ - #include - #include +#include +#include - main(int argc, char *argv[]) { - int Handle; - unsigned char Byte; - unsigned int Checksum; - int ReadLength; +main(int argc, char *argv[]) { + int Handle; + unsigned char Byte; + unsigned int Checksum; + int ReadLength; - if ( argc != 2 ) { - 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]); - exit(1); - } - if ( !ChecksumFile(Handle, &Checksum) ) { - printf("Error reading file %s\n", argv[1]); - exit(1); - } + if ( argc != 2 ) { + 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]); + exit(1); + } + if ( !ChecksumFile(Handle, &Checksum) ) { + printf("Error reading file %s\n", argv[1]); + exit(1); + } - /* Report the result */ - printf("The checksum is: %u\n", Checksum); - exit(0); - } + /* Report the result */ + printf("The checksum is: %u\n", Checksum); + exit(0); +} +``` **LISTING 1.3 L1-3.ASM** - ; Assembler subroutine to perform a 16-bit checksum on the file - ; opened on the passed-in handle. Stores the result in the - ; passed-in checksum variable. Returns 1 for success, 0 for error. - ; - ; Call as: - ; int ChecksumFile(unsigned int Handle, unsigned int *Checksum); - ; - ; where: - ; Handle = handle # under which file to checksum is open - ; Checksum = pointer to unsigned int variable checksum is - ; to be stored in - ; - ; Parameter structure: - ; - Parms struc - dw ? ;pushed BP - dw ? ;return address - Handle dw ? - Checksum dw ? - Parms ends - ; - .model small - .data - TempWord label word - TempByte db ? ;each byte read by DOS will be stored here - db 0 ;high byte of TempWord is always 0 - ;for 16-bit adds - ; - .code - public _ChecksumFile - _ChecksumFile proc near - push bp - mov bp,sp - push si ;save C's register variable - ; - mov bx,[bp+Handle] ;get file handle - sub si,si ;zero the checksum ;accumulator - mov cx,1 ;request one byte on each ;read - mov dx,offset TempByte ;point DX to the byte in - ;which DOS should store - ;each byte read - ChecksumLoop: - mov ah,3fh ;DOS read file function # - 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 - add si,[TempWord] ;add the byte into the - ;checksum total - jmpChecksumLoop - ErrorEnd: - sub ax,ax ;error - jmp short Done - Success: - mov bx,[bp+Checksum] ;point to the checksum variable - mov [bx],si ;save the new checksum - mov ax,1 ;success - ; - Done: - pop si ;restore C's register variable - pop bp - ret - _ChecksumFileendp - end +```nasm +; Assembler subroutine to perform a 16-bit checksum on the file +; opened on the passed-in handle. Stores the result in the +; passed-in checksum variable. Returns 1 for success, 0 for error. +; +; Call as: +; int ChecksumFile(unsigned int Handle, unsigned int *Checksum); +; +; where: +; Handle = handle # under which file to checksum is open +; Checksum = pointer to unsigned int variable checksum is +; to be stored in +; +; Parameter structure: +; +Parms struc + dw ? ;pushed BP + dw ? ;return address +Handle dw ? +Checksum dw ? +Parms ends +; + .model small + .data +TempWord label word +TempByte db ? ;each byte read by DOS will be stored here + db 0 ;high byte of TempWord is always 0 + ;for 16-bit adds +; + .code + public _ChecksumFile +_ChecksumFile proc near + push bp + mov bp,sp + push si ;save C's register variable +; + mov bx,[bp+Handle] ;get file handle + sub si,si ;zero the checksum ;accumulator + mov cx,1 ;request one byte on each ;read + mov dx,offset TempByte ;point DX to the byte in + ;which DOS should store + ;each byte read +ChecksumLoop: + mov ah,3fh ;DOS read file function # + 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 + add si,[TempWord] ;add the byte into the + ;checksum total +jmpChecksumLoop +ErrorEnd: + sub ax,ax ;error + jmp short Done +Success: + mov bx,[bp+Checksum] ;point to the checksum variable + mov [bx],si ;save the new checksum + mov ax,1 ;success +; +Done: + pop si ;restore C's register variable + pop bp + ret +_ChecksumFileendp + end +``` The lesson is clear: Optimization makes code faster, but without proper design, optimization just creates fast slow code. diff --git a/01-04.md b/01-04.md index 9530afa..ff64474 100644 --- a/01-04.md +++ b/01-04.md @@ -20,39 +20,41 @@ libraries do their work. In other words, *know the territory*! **LISTING 1.4 L1-4.C** - /* - * Program to calculate the 16-bit checksum of the stream of bytes - * from the specified file. Obtains the bytes one at a time via - * getc(), allowing C to perform data buffering. - */ - #include +```c +/* +* Program to calculate the 16-bit checksum of the stream of bytes +* from the specified file. Obtains the bytes one at a time via +* getc(), allowing C to perform data buffering. +*/ +#include - main(int argc, char *argv[]) { - FILE *CheckFile; - int Byte; - unsigned int Checksum; +main(int argc, char *argv[]) { + FILE *CheckFile; + int Byte; + unsigned int Checksum; - if ( argc != 2 ) { - printf("usage: checksum filename\n"); - exit(1); - } - if ( (CheckFile = fopen(argv[1], "rb")) == NULL ) { - printf("Can't open file: %s\n", argv[1]); - exit(1); - } + if ( argc != 2 ) { + printf("usage: checksum filename\n"); + exit(1); + } + if ( (CheckFile = fopen(argv[1], "rb")) == NULL ) { + printf("Can't open file: %s\n", argv[1]); + exit(1); + } - /* Initialize the checksum accumulator */ - Checksum = 0; + /* Initialize the checksum accumulator */ + Checksum = 0; - /* Add each byte in turn into the checksum accumulator */ - while ( (Byte = getc(CheckFile)) != EOF ) { - Checksum += (unsigned int) Byte; - } + /* Add each byte in turn into the checksum accumulator */ + while ( (Byte = getc(CheckFile)) != EOF ) { + Checksum += (unsigned int) Byte; + } - /* Report the result */ - printf("The checksum is: %u\n", Checksum); - exit(0); - } + /* Report the result */ + printf("The checksum is: %u\n", Checksum); + exit(0); +} +``` #### Know When It Matters {#Heading10} diff --git a/01-05.md b/01-05.md index 27ed749..14bd6b6 100644 --- a/01-05.md +++ b/01-05.md @@ -30,62 +30,64 @@ uses no assembly at all. **LISTING 1.5 L1-5.C** - /* - * Program to calculate the 16-bit checksum of the stream of bytes - * from the specified file. Buffers the bytes internally, rather - * than letting C or DOS do the work. - */ - #include - #include - #include /* alloc.h for Borland, - malloc.h for Microsoft */ +```c +/* +* Program to calculate the 16-bit checksum of the stream of bytes +* from the specified file. Buffers the bytes internally, rather +* than letting C or DOS do the work. +*/ +#include +#include +#include /* alloc.h for Borland, + malloc.h for Microsoft */ - #define BUFFER_SIZE 0x8000 /* 32Kb data buffer */ +#define BUFFER_SIZE 0x8000 /* 32Kb data buffer */ - main(int argc, char *argv[]) { - int Handle; - unsigned int Checksum; - unsigned char *WorkingBuffer, *WorkingPtr; - int WorkingLength, LengthCount; +main(int argc, char *argv[]) { + int Handle; + unsigned int Checksum; + unsigned char *WorkingBuffer, *WorkingPtr; + int WorkingLength, LengthCount; - if ( argc != 2 ) { - 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]); - exit(1); - } + if ( argc != 2 ) { + 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]); + exit(1); + } - /* Get memory in which to buffer the data */ - if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) { - printf("Can't get enough memory\n"); - exit(1); - } + /* Get memory in which to buffer the data */ + if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) { + printf("Can't get enough memory\n"); + exit(1); + } - /* Initialize the checksum accumulator */ - Checksum = 0; + /* Initialize the checksum accumulator */ + Checksum = 0; - /* Process the file in BUFFER_SIZE chunks */ - do { - if ( (WorkingLength = read(Handle, WorkingBuffer, - BUFFER_SIZE)) == -1 ) { - printf("Error reading file %s\n", argv[1]); - exit(1); - } - /* Checksum this chunk */ - WorkingPtr = WorkingBuffer; - LengthCount = WorkingLength; - while ( LengthCount-- ) { - /* Add each byte in turn into the checksum accumulator */ - Checksum += (unsigned int) *WorkingPtr++; - } - } while ( WorkingLength ); + /* Process the file in BUFFER_SIZE chunks */ + do { + if ( (WorkingLength = read(Handle, WorkingBuffer, + BUFFER_SIZE)) == -1 ) { + printf("Error reading file %s\n", argv[1]); + exit(1); + } + /* Checksum this chunk */ + WorkingPtr = WorkingBuffer; + LengthCount = WorkingLength; + while ( LengthCount-- ) { + /* Add each byte in turn into the checksum accumulator */ + Checksum += (unsigned int) *WorkingPtr++; + } + } while ( WorkingLength ); - /* Report the result */ - printf("The checksum is: %u\n", Checksum); - exit(0); - } + /* Report the result */ + 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 @@ -122,56 +124,58 @@ the design has been maxed out. **LISTING 1.6 L1-6.C** - /* - * Program to calculate the 16-bit checksum of the stream of bytes - * from the specified file. Buffers the bytes internally, rather - * than letting C or DOS do the work, with the time-critical - * portion of the code written in optimized assembler. - */ - #include - #include - #include /* alloc.h for Borland, - malloc.h for Microsoft */ +```c +/* +* Program to calculate the 16-bit checksum of the stream of bytes +* from the specified file. Buffers the bytes internally, rather +* than letting C or DOS do the work, with the time-critical +* portion of the code written in optimized assembler. +*/ +#include +#include +#include /* alloc.h for Borland, + malloc.h for Microsoft */ - #define BUFFER_SIZE 0x8000 /* 32K data buffer */ +#define BUFFER_SIZE 0x8000 /* 32K data buffer */ - main(int argc, char *argv[]) { - int Handle; - unsigned int Checksum; - unsigned char *WorkingBuffer; - int WorkingLength; +main(int argc, char *argv[]) { + int Handle; + unsigned int Checksum; + unsigned char *WorkingBuffer; + int WorkingLength; - if ( argc != 2 ) { - 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]); - exit(1); - } + if ( argc != 2 ) { + 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]); + exit(1); + } - /* Get memory in which to buffer the data */ - if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) { - printf("Can't get enough memory\n"); - exit(1); - } + /* Get memory in which to buffer the data */ + if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) { + printf("Can't get enough memory\n"); + exit(1); + } - /* Initialize the checksum accumulator */ - Checksum = 0; + /* Initialize the checksum accumulator */ + Checksum = 0; - /* Process the file in 32K chunks */ - do { - if ( (WorkingLength = read(Handle, WorkingBuffer, - BUFFER_SIZE)) == -1 ) { - printf("Error reading file %s\n", argv[1]); - exit(1); - } - /* Checksum this chunk if there's anything in it */ - if ( WorkingLength ) - ChecksumChunk(WorkingBuffer, WorkingLength, &Checksum); - } while ( WorkingLength ); + /* Process the file in 32K chunks */ + do { + if ( (WorkingLength = read(Handle, WorkingBuffer, + BUFFER_SIZE)) == -1 ) { + printf("Error reading file %s\n", argv[1]); + exit(1); + } + /* 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); - exit(0); - } + /* Report the result */ + printf("The checksum is: %u\n", Checksum); + exit(0); +} +``` diff --git a/01-06.md b/01-06.md index a27f073..34e0f59 100644 --- a/01-06.md +++ b/01-06.md @@ -12,55 +12,57 @@ pages: 018-019 **LISTING 1.7 L1-7.ASM** - ; Assembler subroutine to perform a 16-bit checksum on a block of - ; bytes 1 to 64K in size. Adds checksum for block into passed-in - ; checksum. - ; - ; Call as: - ; void ChecksumChunk(unsigned char *Buffer, - ; unsigned int BufferLength, unsigned int *Checksum); - ; - ; where: - ; Buffer = pointer to start of block of bytes to checksum - ; BufferLength = # of bytes to checksum (0 means 64K, not 0) - ; Checksum = pointer to unsigned int variable checksum is - ;stored in - ; - ; Parameter structure: - ; - Parms struc - dw ? ;pushed BP - dw ? ;return address - Buffer dw ? - BufferLength dw ? - Checksum dw ? - Parmsends - ; - .model small - .code - public _ChecksumChunk - _ChecksumChunkprocnear - push bp - mov bp,sp - push si ;save C's register variable - ; - cld ;make LODSB increment SI - mov si,[bp+Buffer] ;point to buffer - mov cx,[bp+BufferLength] ;get buffer length - mov bx,[bp+Checksum] ;point to checksum variable - mov dx,[bx] ;get the current checksum - sub ah,ah ;so AX will be a 16-bit value after LODSB - ChecksumLoop: - lodsb ;get the next byte - add dx,ax ;add it into the checksum total - loop ChecksumLoop ;continue for all bytes in block - mov [bx],dx ;save the new checksum - ; - pop si ;restore C's register variable - pop bp - ret - _ChecksumChunkendp - end +```nasm +; Assembler subroutine to perform a 16-bit checksum on a block of +; bytes 1 to 64K in size. Adds checksum for block into passed-in +; checksum. +; +; Call as: +; void ChecksumChunk(unsigned char *Buffer, +; unsigned int BufferLength, unsigned int *Checksum); +; +; where: +; Buffer = pointer to start of block of bytes to checksum +; BufferLength = # of bytes to checksum (0 means 64K, not 0) +; Checksum = pointer to unsigned int variable checksum is +;stored in +; +; Parameter structure: +; +Parms struc + dw ? ;pushed BP + dw ? ;return address +Buffer dw ? +BufferLength dw ? +Checksum dw ? +Parmsends +; + .model small + .code + public _ChecksumChunk +_ChecksumChunkprocnear + push bp + mov bp,sp + push si ;save C's register variable +; + cld ;make LODSB increment SI + mov si,[bp+Buffer] ;point to buffer + mov cx,[bp+BufferLength] ;get buffer length + mov bx,[bp+Checksum] ;point to checksum variable + mov dx,[bx] ;get the current checksum + sub ah,ah ;so AX will be a 16-bit value after LODSB +ChecksumLoop: + lodsb ;get the next byte + add dx,ax ;add it into the checksum total + loop ChecksumLoop ;continue for all bytes in block + mov [bx],dx ;save the new checksum +; + pop si ;restore C's register variable + pop bp + ret +_ChecksumChunkendp + end +``` 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. diff --git a/02-01.md b/02-01.md index fa33ac5..2266ce2 100644 --- a/02-01.md +++ b/02-01.md @@ -45,14 +45,16 @@ 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 done, the key part of the code looked something like this: - LoopTop: - lodsb ;get the next byte to extract a bit from - and al,ah ;isolate the bit we want - rol al,cl ;rotate the bit into the desired position - or bl,al ;insert the bit into the final nibble - dec cx ;the next bit goes 1 place to the right - dec dx ;count down the number of bits - jnz LoopTop ;process the next bit, if any +```nasm +LoopTop: + lodsb ;get the next byte to extract a bit from + and al,ah ;isolate the bit we want + rol al,cl ;rotate the bit into the desired position + or bl,al ;insert the bit into the final nibble + dec cx ;the next bit goes 1 place to the right + 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, only one of which accesses memory, and most programmers would have @@ -68,15 +70,17 @@ total of four separate time-consuming multibit rotations! I changed the code to the following: - LoopTop: - lodsb ;get the next byte to extract a bit from - and al,ah ;isolate the bit we want - or bl,al ;insert the bit into the final nibble - rol bl,1 ;make room for the next bit - dec dx ;count down the number of bits - jnz LoopTop ;process the next bit, if any - rol bl,cl ;rotate all four bits into their final - ; positions at the same time +```nasm +LoopTop: + lodsb ;get the next byte to extract a bit from + and al,ah ;isolate the bit we want + or bl,al ;insert the bit into the final nibble + rol bl,1 ;make room for the next bit + dec dx ;count down the number of bits + jnz LoopTop ;process the next bit, if any + rol bl,cl ;rotate all four bits into their final + ; positions at the same time +``` This moved the costly multibit rotation out of the loop so that it was performed just once, rather than four times. While the code may not look diff --git a/03-02.md b/03-02.md index 7083243..7e3103e 100644 --- a/03-02.md +++ b/03-02.md @@ -12,442 +12,444 @@ pages: 035-042 **LISTING 3.1 PZTIMER.ASM** - ; The precision Zen timer (PZTIMER.ASM) - ; - ; Uses the 8253 timer to time the performance of code that takes - ; less than about 54 milliseconds to execute, with a resolution - ; of better than 10 microseconds. - ; - ; By Michael Abrash - ; - ; Externally callable routines: - ; - ; ZTimerOn: Starts the Zen timer, with interrupts disabled. - ; - ; ZTimerOff: Stops the Zen timer, saves the timer count, - ; times the overhead code, and restores interrupts to the - ; state they were in when ZTimerOn was called. - ; - ; ZTimerReport: Prints the net time that passed between starting - ; and stopping the timer. - ; - ; Note: If longer than about 54 ms passes between ZTimerOn and - ; ZTimerOff calls, the timer turns over and the count is - ; inaccurate. When this happens, an error message is displayed - ; instead of a count. The long-period Zen timer should be used - ; in such cases. - ; - ; Note: Interrupts *MUST* be left off between calls to ZTimerOn - ; and ZTimerOff for accurate timing and for detection of - ; timer overflow. - ; - ; 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 - ; 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 - ; to reboot at the end of each timing session. (The - ; battery-backed clock, if any, is not affected by the Zen - ; timer.) - ; - ; All registers, and all flags except the interrupt flag, are - ; preserved by all routines. Interrupts are enabled and then disabled - ; by ZTimerOn, and are restored by ZTimerOff to the state they were - ; in when ZTimerOn was called. - ; +```nasm +; The precision Zen timer (PZTIMER.ASM) +; +; Uses the 8253 timer to time the performance of code that takes +; less than about 54 milliseconds to execute, with a resolution +; of better than 10 microseconds. +; +; By Michael Abrash +; +; Externally callable routines: +; +; ZTimerOn: Starts the Zen timer, with interrupts disabled. +; +; ZTimerOff: Stops the Zen timer, saves the timer count, +; times the overhead code, and restores interrupts to the +; state they were in when ZTimerOn was called. +; +; ZTimerReport: Prints the net time that passed between starting +; and stopping the timer. +; +; Note: If longer than about 54 ms passes between ZTimerOn and +; ZTimerOff calls, the timer turns over and the count is +; inaccurate. When this happens, an error message is displayed +; instead of a count. The long-period Zen timer should be used +; in such cases. +; +; Note: Interrupts *MUST* be left off between calls to ZTimerOn +; and ZTimerOff for accurate timing and for detection of +; timer overflow. +; +; 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 +; 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 +; to reboot at the end of each timing session. (The +; battery-backed clock, if any, is not affected by the Zen +; timer.) +; +; All registers, and all flags except the interrupt flag, are +; preserved by all routines. Interrupts are enabled and then disabled +; by ZTimerOn, and are restored by ZTimerOff to the state they were +; in when ZTimerOn was called. +; - Code segment word public ‘CODE' - assumecs: Code, ds:nothing - public ZTimerOn, ZTimerOff, ZTimerReport +Code segment word public ‘CODE' + assumecs: Code, ds:nothing + public ZTimerOn, ZTimerOff, ZTimerReport - ; - ; Base address of the 8253 timer chip. - ; - BASE_8253equ40h - ; - ; The address of the timer 0 count registers in the 8253. - ; - TIMER_0_8253 equBASE_8253 + 0 - ; - ; The address of the mode register in the 8253. - ; - MODE_8253 equBASE_8253 + 3 - ; - ; The address of Operation Command Word 3 in the 8259 Programmable - ; Interrupt Controller (PIC) (write only, and writable only when - ; bit 4 of the byte written to this address is 0 and bit 3 is 1). - ; - OCW3 equ20h - ; - ; The address of the Interrupt Request register in the 8259 PIC - ; (read only, and readable only when bit 1 of OCW3 = 1 and bit 0 - ; of OCW3 = 0). - ; - IRR equ20h - ; - ; Macro to emulate a POPF instruction in order to fix the bug in some - ; 80286 chips which allows interrupts to occur during a POPF even when - ; interrupts remain disabled. - ; - MPOPF macro - local p1, p2 - jmp short p2 - p1: iret ; jump to pushed address & pop flags - p2: push cs ; construct far return address to - call p1 ; the next instruction - endm +; +; Base address of the 8253 timer chip. +; +BASE_8253equ40h +; +; The address of the timer 0 count registers in the 8253. +; +TIMER_0_8253 equBASE_8253 + 0 +; +; The address of the mode register in the 8253. +; +MODE_8253 equBASE_8253 + 3 +; +; The address of Operation Command Word 3 in the 8259 Programmable +; Interrupt Controller (PIC) (write only, and writable only when +; bit 4 of the byte written to this address is 0 and bit 3 is 1). +; +OCW3 equ20h +; +; The address of the Interrupt Request register in the 8259 PIC +; (read only, and readable only when bit 1 of OCW3 = 1 and bit 0 +; of OCW3 = 0). +; +IRR equ20h +; +; Macro to emulate a POPF instruction in order to fix the bug in some +; 80286 chips which allows interrupts to occur during a POPF even when +; interrupts remain disabled. +; +MPOPF macro + local p1, p2 + jmp short p2 +p1: iret ; jump to pushed address & pop flags +p2: push cs ; construct far return address to + call p1 ; the next instruction + endm - ; - ; Macro to delay briefly to ensure that enough time has elapsed - ; between successive I/O accesses so that the device being accessed - ; can respond to both accesses even on a very fast PC. - ; - DELAY macro - jmp $+2 - jmp $+2 - jmp $+2 - endm +; +; Macro to delay briefly to ensure that enough time has elapsed +; between successive I/O accesses so that the device being accessed +; can respond to both accesses even on a very fast PC. +; +DELAY macro + jmp $+2 + jmp $+2 + jmp $+2 + endm - OriginalFlags db ? ; storage for upper byte of - ; FLAGS register when - ; ZTimerOn called - TimedCount dw ? ; timer 0 count when the timer - ; is stopped - ReferenceCount dw ; number of counts required to - ; execute timer overhead code - OverflowFlag db ? ; used to indicate whether the - ; timer overflowed during the - ; timing interval - ; - ; String printed to report results. - ; - OutputStr label byte - db 0dh, 0ah, ‘Timed count: ‘, 5 dup (?) - ASCIICountEnd labelbyte - db ‘ microseconds', 0dh, 0ah - db ‘$' - ; - ; String printed to report timer overflow. - ; - OverflowStr label byte - db 0dh, 0ah - db ‘****************************************************' - db 0dh, 0ah - db ‘* The timer overflowed, so the interval timed was *' - db 0dh, 0ah - db ‘* too long for the precision timer to measure. *' - db 0dh, 0ah - db ‘* Please perform the timing test again with the *' - db0dh, 0ah - db ‘* long-period timer. *' - db 0dh, 0ah - db ‘****************************************************' - db 0dh, 0ah - db ‘$' +OriginalFlags db ? ; storage for upper byte of + ; FLAGS register when + ; ZTimerOn called +TimedCount dw ? ; timer 0 count when the timer + ; is stopped +ReferenceCount dw ; number of counts required to + ; execute timer overhead code +OverflowFlag db ? ; used to indicate whether the + ; timer overflowed during the + ; timing interval +; +; String printed to report results. +; +OutputStr label byte + db 0dh, 0ah, ‘Timed count: ‘, 5 dup (?) +ASCIICountEnd labelbyte + db ‘ microseconds', 0dh, 0ah + db ‘$' +; +; String printed to report timer overflow. +; +OverflowStr label byte + db 0dh, 0ah + db ‘****************************************************' + db 0dh, 0ah + db ‘* The timer overflowed, so the interval timed was *' + db 0dh, 0ah + db ‘* too long for the precision timer to measure. *' + db 0dh, 0ah + db ‘* Please perform the timing test again with the *' +db0dh, 0ah + db ‘* long-period timer. *' + db 0dh, 0ah + db ‘****************************************************' + db 0dh, 0ah + db ‘$' - ; ******************************************************************** - ; * Routine called to start timing. * - ; ******************************************************************** +; ******************************************************************** +; * Routine called to start timing. * +; ******************************************************************** - ZTimerOn proc near +ZTimerOn proc near + +; +; Save the context of the program being timed. +; + push ax + pushf + pop ax ; get flags so we can keep + ; interrupts off when leaving + ; this routine + mov cs:[OriginalFlags],ah ; remember the state of the + ; Interrupt flag + and ah,0fdh ; set pushed interrupt flag + ; to 0 + push ax +; +; Turn on interrupts, so the timer interrupt can occur if it's +; pending. +; + sti +; +; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause +; linear counting rather than count-by-two counting. Also +; leaves the 8253 waiting for the initial timer 0 count to +; be loaded. +; + mov al,00110100b ;mode 2 + out MODE_8253,al +; +; 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. +; + DELAY + sub al,al + out TIMER_0_8253,al ;lsb + DELAY + out TIMER_0_8253,al ;msb +; +; Wait before clearing interrupts to allow the interrupt generated +; when switching from mode 3 to mode 2 to be recognized. The delay +; must be at least 210 ns long to allow time for that interrupt to +; occur. Here, 10 jumps are used for the delay to ensure that the +; delay time will be more than long enough even on a very fast PC. +; + rept 10 + jmp $+2 + endm +; +; Disable interrupts to get an accurate count. +; + cli +; +; Set the timer count to 0 again to start the timing interval. +; + mov al,00110100b ; set up to load initial + out MODE_8253,al ; timer count + DELAY + sub al,al + out TIMER_0_8253,al ; load count lsb + DELAY + out TIMER_0_8253,al; load count msb +; +; Restore the context and return. +; + MPOPF ; keeps interrupts off + pop ax + ret + +ZTimerOn endp + +;******************************************************************** +;* Routine called to stop timing and get count. * +;******************************************************************** + +ZTimerOff proc near + +; +; Save the context of the program being timed. +; + push ax + push cx + pushf +; +; Latch the count. +; + mov al,00000000b ; latch timer 0 + out MODE_8253,al +; +; See if the timer has overflowed by checking the 8259 for a pending +; timer interrupt. +; + mov al,00001010b ; OCW3, set up to read + out OCW3,al; Int errupt Request register + DELAY + ina l,IRR; read Interrupt Request + ; register + and al,1 ; set AL to 1 if IRQ0 (the + ; timer interrupt) is pending + mov cs:[OverflowFlag],al; store the timer overflow + ; status +; +; Allow interrupts to happen again. +; + sti +; +; Read out the count we latched earlier. +; + in al,TIMER_0_8253 ; least significant byte + DELAY + mov ah,al + in al,TIMER_0_8253 ; most significant byte + xchg ah,al + neg ax ; convert from countdown + ; remaining to elapsed + ; count + mov cs:[TimedCount],ax +; Time a zero-length code fragment, to get a reference for how +; much overhead this routine has. Time it 16 times and average it, +; for accuracy, rounding the result. +; + mov cs:[ReferenceCount],0 + mov cx,16 + cli ; interrupts off to allow a + ; precise reference count + RefLoop: + call ReferenceZTimerOn + call ReferenceZTimerOff + loop RefLoop + sti + add cs:[ReferenceCount],8; total + (0.5 * 16) + mov cl,4 + shr cs:[ReferenceCount],cl; (total) / 16 + 0.5 +; +; Restore original interrupt state. +; + pop ax ; retrieve flags when called + mov ch,cs:[OriginalFlags] ; get back the original upper + ; byte of the FLAGS register + and ch,not 0fdh ; only care about original + ; interrupt flag... + and ah,0fdh ; ...keep all other flags in + ; their current condition + or ah,ch ; make flags word with original + ; interrupt flag + push ax ; prepare flags to be popped +; +; Restore the context of the program being timed and return to it. +; + MPOPF ; restore the flags with the + ; original interrupt state + pop cx + pop ax + ret + +ZTimerOff endp + +; +; Called by ZTimerOff to start timer for overhead measurements. +; + +ReferenceZTimerOnproc near +; +; Save the context of the program being timed. +; + push ax + pushf ; interrupts are already off +; +; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause +; linear counting rather than count-by-two counting. +; + mov al,00110100b ; set up to load + out MODE_8253,al ; initial timer count + DELAY +; +; Set the timer count to 0. +; + sub al,al + out TIMER_0_8253,al; load count lsb + DELAY + out TIMER_0_8253,al; load count msb +; +; Restore the context of the program being timed and return to it. +; + MPOPF + pop ax + ret + +ReferenceZTimerOnendp + +; +; Called by ZTimerOff to stop timer and add result to ReferenceCount +; for overhead measurements. +; + +ReferenceZTimerOff proc near +; +; Save the context of the program being timed. +; + push ax + push cx + pushf +; +; Latch the count and read it. +; + mov al,00000000b ; latch timer 0 + out MODE_8253,al + DELAY + in al,TIMER_0_8253 ; lsb + DELAY + mov ah,al + in al,TIMER_0_8253 ; msb + xchg ah,al + neg ax ; convert from countdown + ; remaining to amount + ; counted down + add cs:[ReferenceCount],ax +; +; Restore the context of the program being timed and return to it. +; + MPOPF + pop cx + pop ax + ret + +ReferenceZTimerOff endp + +; ******************************************************************** +; * Routine called to report timing results. * +; ******************************************************************** + +ZTimerReport procnear - ; - ; Save the context of the program being timed. - ; - push ax pushf - pop ax ; get flags so we can keep - ; interrupts off when leaving - ; this routine - mov cs:[OriginalFlags],ah ; remember the state of the - ; Interrupt flag - and ah,0fdh ; set pushed interrupt flag - ; to 0 - push ax - ; - ; Turn on interrupts, so the timer interrupt can occur if it's - ; pending. - ; - sti - ; - ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause - ; linear counting rather than count-by-two counting. Also - ; leaves the 8253 waiting for the initial timer 0 count to - ; be loaded. - ; - mov al,00110100b ;mode 2 - out MODE_8253,al - ; - ; 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. - ; - DELAY - sub al,al - out TIMER_0_8253,al ;lsb - DELAY - out TIMER_0_8253,al ;msb - ; - ; Wait before clearing interrupts to allow the interrupt generated - ; when switching from mode 3 to mode 2 to be recognized. The delay - ; must be at least 210 ns long to allow time for that interrupt to - ; occur. Here, 10 jumps are used for the delay to ensure that the - ; delay time will be more than long enough even on a very fast PC. - ; - rept 10 - jmp $+2 - endm - ; - ; Disable interrupts to get an accurate count. - ; - cli - ; - ; Set the timer count to 0 again to start the timing interval. - ; - mov al,00110100b ; set up to load initial - out MODE_8253,al ; timer count - DELAY - sub al,al - out TIMER_0_8253,al ; load count lsb - DELAY - out TIMER_0_8253,al; load count msb - ; - ; Restore the context and return. - ; - MPOPF ; keeps interrupts off - pop ax - ret + push ax + push bx + push cx + push dx + push si + push ds +; + push cs ; DOS functions require that DS point + pop ds ; to text to be displayed on the screen + assume ds :Code +; +; Check for timer 0 overflow. +; + cmp [OverflowFlag],0 + jz PrintGoodCount + mov dx,offset OverflowStr + mov ah,9 + int 21h + jmp short EndZTimerReport +; +; Convert net count to decimal ASCII in microseconds. +; +PrintGoodCount: + mov ax,[TimedCount] + sub ax,[ReferenceCount] + mov si,offset ASCIICountEnd - 1 +; +; Convert count to microseconds by multiplying by .8381. +; + mov dx, 8381 + mul dx + mov bx, 10000 + div bx ;* .8381 = * 8381 / 10000 +; +; Convert time in microseconds to 5 decimal ASCII digits. +; + mov bx, 10 + mov cx, 5 +CTSLoop: + sub dx, dx + div bx + add dl,'0' + mov [si],dl + dec si + loop CTSLoop +; +; Print the results. +; + mov ah, 9 + mov dx, offset OutputStr + int 21h +; +EndZTimerReport: + pop ds + pop si + pop dx + pop cx + pop bx + pop ax + MPOPF + ret - ZTimerOn endp +ZTimerReport endp - ;******************************************************************** - ;* Routine called to stop timing and get count. * - ;******************************************************************** - - ZTimerOff proc near - - ; - ; Save the context of the program being timed. - ; - push ax - push cx - pushf - ; - ; Latch the count. - ; - mov al,00000000b ; latch timer 0 - out MODE_8253,al - ; - ; See if the timer has overflowed by checking the 8259 for a pending - ; timer interrupt. - ; - mov al,00001010b ; OCW3, set up to read - out OCW3,al; Int errupt Request register - DELAY - ina l,IRR; read Interrupt Request - ; register - and al,1 ; set AL to 1 if IRQ0 (the - ; timer interrupt) is pending - mov cs:[OverflowFlag],al; store the timer overflow - ; status - ; - ; Allow interrupts to happen again. - ; - sti - ; - ; Read out the count we latched earlier. - ; - in al,TIMER_0_8253 ; least significant byte - DELAY - mov ah,al - in al,TIMER_0_8253 ; most significant byte - xchg ah,al - neg ax ; convert from countdown - ; remaining to elapsed - ; count - mov cs:[TimedCount],ax - ; Time a zero-length code fragment, to get a reference for how - ; much overhead this routine has. Time it 16 times and average it, - ; for accuracy, rounding the result. - ; - mov cs:[ReferenceCount],0 - mov cx,16 - cli ; interrupts off to allow a - ; precise reference count - RefLoop: - call ReferenceZTimerOn - call ReferenceZTimerOff - loop RefLoop - sti - add cs:[ReferenceCount],8; total + (0.5 * 16) - mov cl,4 - shr cs:[ReferenceCount],cl; (total) / 16 + 0.5 - ; - ; Restore original interrupt state. - ; - pop ax ; retrieve flags when called - mov ch,cs:[OriginalFlags] ; get back the original upper - ; byte of the FLAGS register - and ch,not 0fdh ; only care about original - ; interrupt flag... - and ah,0fdh ; ...keep all other flags in - ; their current condition - or ah,ch ; make flags word with original - ; interrupt flag - push ax ; prepare flags to be popped - ; - ; Restore the context of the program being timed and return to it. - ; - MPOPF ; restore the flags with the - ; original interrupt state - pop cx - pop ax - ret - - ZTimerOff endp - - ; - ; Called by ZTimerOff to start timer for overhead measurements. - ; - - ReferenceZTimerOnproc near - ; - ; Save the context of the program being timed. - ; - push ax - pushf ; interrupts are already off - ; - ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause - ; linear counting rather than count-by-two counting. - ; - mov al,00110100b ; set up to load - out MODE_8253,al ; initial timer count - DELAY - ; - ; Set the timer count to 0. - ; - sub al,al - out TIMER_0_8253,al; load count lsb - DELAY - out TIMER_0_8253,al; load count msb - ; - ; Restore the context of the program being timed and return to it. - ; - MPOPF - pop ax - ret - - ReferenceZTimerOnendp - - ; - ; Called by ZTimerOff to stop timer and add result to ReferenceCount - ; for overhead measurements. - ; - - ReferenceZTimerOff proc near - ; - ; Save the context of the program being timed. - ; - push ax - push cx - pushf - ; - ; Latch the count and read it. - ; - mov al,00000000b ; latch timer 0 - out MODE_8253,al - DELAY - in al,TIMER_0_8253 ; lsb - DELAY - mov ah,al - in al,TIMER_0_8253 ; msb - xchg ah,al - neg ax ; convert from countdown - ; remaining to amount - ; counted down - add cs:[ReferenceCount],ax - ; - ; Restore the context of the program being timed and return to it. - ; - MPOPF - pop cx - pop ax - ret - - ReferenceZTimerOff endp - - ; ******************************************************************** - ; * Routine called to report timing results. * - ; ******************************************************************** - - ZTimerReport procnear - - pushf - push ax - push bx - push cx - push dx - push si - push ds - ; - push cs ; DOS functions require that DS point - pop ds ; to text to be displayed on the screen - assume ds :Code - ; - ; Check for timer 0 overflow. - ; - cmp [OverflowFlag],0 - jz PrintGoodCount - mov dx,offset OverflowStr - mov ah,9 - int 21h - jmp short EndZTimerReport - ; - ; Convert net count to decimal ASCII in microseconds. - ; - PrintGoodCount: - mov ax,[TimedCount] - sub ax,[ReferenceCount] - mov si,offset ASCIICountEnd - 1 - ; - ; Convert count to microseconds by multiplying by .8381. - ; - mov dx, 8381 - mul dx - mov bx, 10000 - div bx ;* .8381 = * 8381 / 10000 - ; - ; Convert time in microseconds to 5 decimal ASCII digits. - ; - mov bx, 10 - mov cx, 5 - CTSLoop: - sub dx, dx - div bx - add dl,'0' - mov [si],dl - dec si - loop CTSLoop - ; - ; Print the results. - ; - mov ah, 9 - mov dx, offset OutputStr - int 21h - ; - EndZTimerReport: - pop ds - pop si - pop dx - pop cx - pop bx - pop ax - MPOPF - ret - - ZTimerReport endp - - Code ends - end +Code ends + end +``` diff --git a/03-05.md b/03-05.md index f6085b5..bae43eb 100644 --- a/03-05.md +++ b/03-05.md @@ -82,43 +82,45 @@ and should contain calls to **ZTimerOn** and **ZTimerOff** . **LISTING 3.2 PZTEST.ASM** - ; Program to measure performance of code that takes less than - ; 54 ms to execute. (PZTEST.ASM) - ; - ; Link with PZTIMER.ASM (Listing 3.1). PZTEST.BAT (Listing 3.4) - ; can be used to assemble and link both files. Code to be - ; measured must be in the file TESTCODE; Listing 3.3 shows - ; a sample TESTCODE file. - ; - ; By Michael Abrash - ; - mystack segment para stack ‘STACK' - db 512 dup(?) - mystack ends - ; - Code segment para public ‘CODE' - assume cs:Code, ds:Code - extrnZTimerOn:near, ZTimerOff:near, ZTimerReport:near - Start proc near - push cs - pop ds ; set DS to point to the code segment, - ; so data as well as code can easily - ; be included in TESTCODE - ; - include TESTCODE ;code to be measured, including - ; calls to ZTimerOn and ZTimerOff - ; - ; Display the results. - ; - call ZTimerReport - ; - ; Terminate the program. - ; - mov ah,4ch - int 21h - Start endp - Code ends - end Start +```nasm +; Program to measure performance of code that takes less than +; 54 ms to execute. (PZTEST.ASM) +; +; Link with PZTIMER.ASM (Listing 3.1). PZTEST.BAT (Listing 3.4) +; can be used to assemble and link both files. Code to be +; measured must be in the file TESTCODE; Listing 3.3 shows +; a sample TESTCODE file. +; +; By Michael Abrash +; +mystack segment para stack ‘STACK' + db 512 dup(?) +mystack ends +; +Code segment para public ‘CODE' + assume cs:Code, ds:Code + extrnZTimerOn:near, ZTimerOff:near, ZTimerReport:near +Start proc near + push cs + pop ds ; set DS to point to the code segment, + ; so data as well as code can easily + ; be included in TESTCODE +; + include TESTCODE ;code to be measured, including + ; calls to ZTimerOn and ZTimerOff +; +; Display the results. +; + call ZTimerReport +; +; Terminate the program. +; + mov ah,4ch + int 21h +Start endp +Code ends + end Start +``` Listing 3.3 shows some sample code to be timed. This listing measures the time required to execute 1,000 loads of AL from the memory variable @@ -130,30 +132,32 @@ after the code in Listing 3.3 has been run. **LISTING 3.3 LST3-3.ASM** - ; Test file; - ; Measures the performance of 1,000 loads of AL from - ; memory. (Use by renaming to TESTCODE, which is - ; included by PZTEST.ASM (Listing 3.2). PZTIME.BAT - ; (Listing 3.4) does this, along with all assembly - ; and linking.) - ; - jmp Skip ;jump around defined data - ; - MemVar db ? - ; - Skip: - ; - ; Start timing. - ; - call ZTimerOn - ; - rept 1000 - mov al,[MemVar] - endm - ; - ; Stop timing. - ; - call ZTimerOff +```nasm +; Test file; +; Measures the performance of 1,000 loads of AL from +; memory. (Use by renaming to TESTCODE, which is +; included by PZTEST.ASM (Listing 3.2). PZTIME.BAT +; (Listing 3.4) does this, along with all assembly +; and linking.) +; +jmp Skip ;jump around defined data +; +MemVar db ? +; +Skip: +; +; Start timing. +; + call ZTimerOn +; + rept 1000 + mov al,[MemVar] + endm +; +; Stop timing. +; + call ZTimerOff +``` 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 diff --git a/03-06.md b/03-06.md index db70d86..78ef405 100644 --- a/03-06.md +++ b/03-06.md @@ -25,72 +25,76 @@ and "link" with "tlink" in Listing 3.4. The same is true of Listing **LISTING 3.4 PZTIME.BAT** - echo off - rem - rem *** Listing 3.4 *** - rem - rem *************************************************************** - rem * Batch file PZTIME.BAT, which builds and runs the precision * - 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 * * - rem * pztime lst3-3 * - rem * * - rem * Note that MASM and LINK must be in the current directory or * - rem * on the current path in order for this batch file to work. * - rem * * - rem * This batch file can be speeded up by assembling PZTIMER.ASM * - rem * once, then removing the lines: * - rem * * - rem * masm pztimer; * - rem * if errorlevel 1 goto errorend * - rem * * - rem * from this file. * - rem * * - rem * By Michael Abrash * - rem *************************************************************** - rem - rem Make sure a file to test was specified. - rem - if not x%1==x goto ckexist - echo *************************************************************** - echo * Please specify a file to test. * - echo *************************************************************** - goto end - rem - rem Make sure the file exists. - rem - :ckexist - if exist %1 goto docopy - echo *************************************************************** - echo * The specified file, "%1," doesn't exist. * - echo *************************************************************** - goto end - rem - rem copy the file to measure to TESTCODE. - rem - :docopy - copy %1 testcode - masm pztest; - if errorlevel 1 goto errorend - masm pztimer; - if errorlevel 1 goto errorend - link pztest+pztimer; - if errorlevel 1 goto errorend - pztest - goto end - :errorend - echo *************************************************************** - echo * An error occurred while building the precision Zen timer. * - echo *************************************************************** - :end +```bat +echo off +rem +rem *** Listing 3.4 *** +rem +rem *************************************************************** +rem * Batch file PZTIME.BAT, which builds and runs the precision * +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 * * +rem * pztime lst3-3 * +rem * * +rem * Note that MASM and LINK must be in the current directory or * +rem * on the current path in order for this batch file to work. * +rem * * +rem * This batch file can be speeded up by assembling PZTIMER.ASM * +rem * once, then removing the lines: * +rem * * +rem * masm pztimer; * +rem * if errorlevel 1 goto errorend * +rem * * +rem * from this file. * +rem * * +rem * By Michael Abrash * +rem *************************************************************** +rem +rem Make sure a file to test was specified. +rem +if not x%1==x goto ckexist +echo *************************************************************** +echo * Please specify a file to test. * +echo *************************************************************** +goto end +rem +rem Make sure the file exists. +rem +:ckexist +if exist %1 goto docopy +echo *************************************************************** +echo * The specified file, "%1," doesn't exist. * +echo *************************************************************** +goto end +rem +rem copy the file to measure to TESTCODE. +rem +:docopy +copy %1 testcode +masm pztest; +if errorlevel 1 goto errorend +masm pztimer; +if errorlevel 1 goto errorend +link pztest+pztimer; +if errorlevel 1 goto errorend +pztest +goto end +:errorend +echo *************************************************************** +echo * An error occurred while building the precision Zen timer. * +echo *************************************************************** +:end +``` Assuming that Listing 3.3 is named LST3-3.ASM and Listing 3.4 is named PZTIME.BAT, the code in Listing 3.3 would be timed with the command: - pztime LST3-3.ASM +```sh +pztime LST3-3.ASM +``` which performs all assembly and linking, and reports the execution time of the code in Listing 3.3. @@ -116,7 +120,9 @@ In order to perform any of the timing tests in this book, enter Listing and enter Listing 3.4 and name it PZTIME.BAT. Then simply enter the listing you wish to run into the file *filename* and enter the command: - pztime +```sh +pztime +``` In fact, that's exactly how I timed each of the listings in this book. Code fragments you write yourself can be timed in just the same way. If diff --git a/03-07.md b/03-07.md index a9682f7..0a5635f 100644 --- a/03-07.md +++ b/03-07.md @@ -78,637 +78,639 @@ computers. **LISTING 3.5 LZTIMER.ASM** - ; - ; The long-period Zen timer. (LZTIMER.ASM) - ; Uses the 8253 timer and the BIOS time-of-day count to time the - ; performance of code that takes less than an hour to execute. - ; Because interrupts are left on (in order to allow the timer - ; interrupt to be recognized), this is less accurate than the - ; precision Zen timer, so it is best used only to time code that takes - ; more than about 54 milliseconds to execute (code that the precision - ; Zen timer reports overflow on). Resolution is limited by the - ; occurrence of timer interrupts. - ; - ; By Michael Abrash - ; - ; Externally callable routines: - ; - ; ZTimerOn: Saves the BIOS time of day count and starts the - ; long-period Zen timer. - ; - ; ZTimerOff: Stops the long-period Zen timer and saves the timer - ; count and the BIOS time-of-day count. - ; - ; ZTimerReport: Prints the time that passed between starting and - ; stopping the timer. - ; - ; Note: If either more than an hour passes or midnight falls between - ; calls to ZTimerOn and ZTimerOff, an error is reported. For - ; timing code that takes more than a few minutes to execute, - ; either the DOS TIME command in a batch file before and after - ; execution of the code to time or the use of the DOS - ; time-of-day function in place of the long-period Zen timer is - ; 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 - ; 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. - ; - ; Note: When PS2 is 0, the code relies on an undocumented 8253 - ; feature to get more reliable readings. It is possible that - ; the 8253 (or whatever chip is emulating the 8253) may be put - ; into an undefined or incorrect state when this feature is - ; used. - ; - ; ****************************************************************** - ; * If your computer displays any hint of erratic behavior * - ; * after the long-period Zen timer is used, such as the floppy* - ; * drive failing to operate properly, reboot the system, set * - ; * PS2 to 1 and leave it that way! * - ; ****************************************************************** - ; - ; Note: Each block of code being timed should ideally be run several - ; times, with at least two similar readings required to - ; establish a true measurement, in order to eliminate any - ; variability caused by interrupts. - ; - ; Note: Interrupts must not be disabled for more than 54 ms at a - ; stretch during the timing interval. Because interrupts - ; are enabled, keys, mice, and other devices that generate - ; interrupts should not be used during the timing interval. - ; - ; Note: Any extra code running off the timer interrupt (such as - ; some memory-resident utilities) will increase the time - ; measured by the Zen timer. - ; - ; 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 - ; reboot at the conclusion of timing sessions. (The - ; battery-backed clock, if any, is not affected by the Zen - ; timer.) - ; - ; All registers and all flags are preserved by all routines. - ; +```nasm +; +; The long-period Zen timer. (LZTIMER.ASM) +; Uses the 8253 timer and the BIOS time-of-day count to time the +; performance of code that takes less than an hour to execute. +; Because interrupts are left on (in order to allow the timer +; interrupt to be recognized), this is less accurate than the +; precision Zen timer, so it is best used only to time code that takes +; more than about 54 milliseconds to execute (code that the precision +; Zen timer reports overflow on). Resolution is limited by the +; occurrence of timer interrupts. +; +; By Michael Abrash +; +; Externally callable routines: +; +; ZTimerOn: Saves the BIOS time of day count and starts the +; long-period Zen timer. +; +; ZTimerOff: Stops the long-period Zen timer and saves the timer +; count and the BIOS time-of-day count. +; +; ZTimerReport: Prints the time that passed between starting and +; stopping the timer. +; +; Note: If either more than an hour passes or midnight falls between +; calls to ZTimerOn and ZTimerOff, an error is reported. For +; timing code that takes more than a few minutes to execute, +; either the DOS TIME command in a batch file before and after +; execution of the code to time or the use of the DOS +; time-of-day function in place of the long-period Zen timer is +; 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 +; 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. +; +; Note: When PS2 is 0, the code relies on an undocumented 8253 +; feature to get more reliable readings. It is possible that +; the 8253 (or whatever chip is emulating the 8253) may be put +; into an undefined or incorrect state when this feature is +; used. +; +; ****************************************************************** +; * If your computer displays any hint of erratic behavior * +; * after the long-period Zen timer is used, such as the floppy* +; * drive failing to operate properly, reboot the system, set * +; * PS2 to 1 and leave it that way! * +; ****************************************************************** +; +; Note: Each block of code being timed should ideally be run several +; times, with at least two similar readings required to +; establish a true measurement, in order to eliminate any +; variability caused by interrupts. +; +; Note: Interrupts must not be disabled for more than 54 ms at a +; stretch during the timing interval. Because interrupts +; are enabled, keys, mice, and other devices that generate +; interrupts should not be used during the timing interval. +; +; Note: Any extra code running off the timer interrupt (such as +; some memory-resident utilities) will increase the time +; measured by the Zen timer. +; +; 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 +; reboot at the conclusion of timing sessions. (The +; battery-backed clock, if any, is not affected by the Zen +; timer.) +; +; All registers and all flags are preserved by all routines. +; - Code segment word public ‘CODE' - assume cs: Code, ds:nothing - public ZTimerOn, ZTimerOff, ZTimerReport +Code segment word public ‘CODE' + assume cs: Code, ds:nothing + public ZTimerOn, ZTimerOff, ZTimerReport - ; - ; Set PS2 to 0 to assemble for use on a fully 8253-compatible - ; 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 - ; overall operation by putting the 8253 into an undefined or - ; incorrect state. Use with caution!!! - ; - ; Set PS2 to 1 to assemble for use on non-8253-compatible - ; systems, including PS/2 computers; when PS2 is 1, readings - ; may occasionally be off by 54 ms, but the code will work - ; properly on all systems. - ; - ; A setting of 1 is safer and will work on more systems, - ; while a setting of 0 produces more reliable results in systems - ; which support the undocumented timer-stopping feature of the - ; 8253. The choice is yours. - ; - PS2 equ1 - ; - ; Base address of the 8253 timer chip. - ; - BASE_8253 equ40h - ; - ; The address of the timer 0 count registers in the 8253. - ; - TIMER_0_8253 equBASE_8253 + 0 - ; - ; The address of the mode register in the 8253. - ; - MODE_8253 equBASE_8253 + 3 - ; - ; The address of the BIOS timer count variable in the BIOS - ; data segment. - ; - TIMER_COUNT equ46ch - ; - ; Macro to emulate a POPF instruction in order to fix the bug in some - ; 80286 chips which allows interrupts to occur during a POPF even when - ; interrupts remain disabled. - ; - MPOPF macro - local p1, p2 - jmp short p2 - p1: iret ;jump to pushed address & pop flags - p2: pushcs ;construct far return address to - call p1 ; the next instruction - endm +; +; Set PS2 to 0 to assemble for use on a fully 8253-compatible +; 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 +; overall operation by putting the 8253 into an undefined or +; incorrect state. Use with caution!!! +; +; Set PS2 to 1 to assemble for use on non-8253-compatible +; systems, including PS/2 computers; when PS2 is 1, readings +; may occasionally be off by 54 ms, but the code will work +; properly on all systems. +; +; A setting of 1 is safer and will work on more systems, +; while a setting of 0 produces more reliable results in systems +; which support the undocumented timer-stopping feature of the +; 8253. The choice is yours. +; +PS2 equ1 +; +; Base address of the 8253 timer chip. +; +BASE_8253 equ40h +; +; The address of the timer 0 count registers in the 8253. +; +TIMER_0_8253 equBASE_8253 + 0 +; +; The address of the mode register in the 8253. +; +MODE_8253 equBASE_8253 + 3 +; +; The address of the BIOS timer count variable in the BIOS +; data segment. +; +TIMER_COUNT equ46ch +; +; Macro to emulate a POPF instruction in order to fix the bug in some +; 80286 chips which allows interrupts to occur during a POPF even when +; interrupts remain disabled. +; +MPOPF macro + local p1, p2 + jmp short p2 +p1: iret ;jump to pushed address & pop flags +p2: pushcs ;construct far return address to + call p1 ; the next instruction +endm - ; - ; Macro to delay briefly to ensure that enough time has elapsed - ; between successive I/O accesses so that the device being accessed - ; can respond to both accesses even on a very fast PC. - ; - DELAY macro - jmp $+2 - jmp $+2 - jmp $+2 - endm +; +; Macro to delay briefly to ensure that enough time has elapsed +; between successive I/O accesses so that the device being accessed +; can respond to both accesses even on a very fast PC. +; +DELAY macro + jmp $+2 + jmp $+2 + jmp $+2 + endm - StartBIOSCountLow dw ? ;BIOS count low word at the - ; start of the timing period - StartBIOSCountHigh dw ? ;BIOS count high word at the - ; start of the timing period - EndBIOSCountLow dw ? ;BIOS count low word at the - ; end of the timing period - EndBIOSCountHigh dw ? ;BIOS count high word at the - ; end of the timing period - EndTimedCount dw ? ;timer 0 count at the end of - ; the timing period - ReferenceCount dw ? ;number of counts required to - ; execute timer overhead code - ; - ; String printed to report results. - ; - OutputStr labelbyte - db 0dh, 0ah, ‘Timed count: ‘ - TimedCountStr db10 dup (?) - db' microseconds', 0dh, 0ah - db ‘$' - ; - ; Temporary storage for timed count as it's divided down by powers - ; of ten when converting from doubleword binary to ASCII. - ; - CurrentCountLow dw ? - CurrentCountHigh dw ? - ; - ; Powers of ten table used to perform division by 10 when doing - ; doubleword conversion from binary to ASCII. - ; - PowersOfTenlabelword - dd 1 - dd 10 - dd 100 - dd 1000 - dd 10000 - dd 100000 - dd 1000000 - dd 10000000 - dd 100000000 - dd 1000000000 - PowersOfTenEnd labelword - ; - ; String printed to report that the high word of the BIOS count - ; changed while timing (an hour elapsed or midnight was crossed), - ; and so the count is invalid and the test needs to be rerun. - ; - TurnOverStrlabelbyte - db 0dh, 0ah - db ‘****************************************************' - db 0dh, 0ah - db'* Either midnight passed or an hour or more passed *' - db 0dh, 0ah - db'* while timing was in progress. If the former was *' - db 0dh, 0ah - 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 0dh, 0ah - db'* run to be timed by the long-period Zen timer. *' - db 0dh, 0ah - db ‘* Suggestions: use the DOS TIME command, the DOS *' - db 0dh, 0ah - db ‘* time function, or a watch. *' - db 0dh, 0ah - db ‘****************************************************' - db 0dh, 0ah - db'$' +StartBIOSCountLow dw ? ;BIOS count low word at the + ; start of the timing period +StartBIOSCountHigh dw ? ;BIOS count high word at the + ; start of the timing period +EndBIOSCountLow dw ? ;BIOS count low word at the + ; end of the timing period +EndBIOSCountHigh dw ? ;BIOS count high word at the + ; end of the timing period +EndTimedCount dw ? ;timer 0 count at the end of + ; the timing period +ReferenceCount dw ? ;number of counts required to + ; execute timer overhead code +; +; String printed to report results. +; +OutputStr labelbyte + db 0dh, 0ah, ‘Timed count: ‘ +TimedCountStr db10 dup (?) + db' microseconds', 0dh, 0ah + db ‘$' +; +; Temporary storage for timed count as it's divided down by powers +; of ten when converting from doubleword binary to ASCII. +; +CurrentCountLow dw ? +CurrentCountHigh dw ? +; +; Powers of ten table used to perform division by 10 when doing +; doubleword conversion from binary to ASCII. +; +PowersOfTenlabelword + dd 1 + dd 10 + dd 100 + dd 1000 + dd 10000 + dd 100000 + dd 1000000 + dd 10000000 + dd 100000000 + dd 1000000000 +PowersOfTenEnd labelword +; +; String printed to report that the high word of the BIOS count +; changed while timing (an hour elapsed or midnight was crossed), +; and so the count is invalid and the test needs to be rerun. +; +TurnOverStrlabelbyte + db 0dh, 0ah + db ‘****************************************************' + db 0dh, 0ah + db'* Either midnight passed or an hour or more passed *' + db 0dh, 0ah + db'* while timing was in progress. If the former was *' + db 0dh, 0ah + 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 0dh, 0ah + db'* run to be timed by the long-period Zen timer. *' + db 0dh, 0ah + db ‘* Suggestions: use the DOS TIME command, the DOS *' + db 0dh, 0ah + db ‘* time function, or a watch. *' + db 0dh, 0ah + db ‘****************************************************' + db 0dh, 0ah + db'$' - ;******************************************************************** - ;* Routine called to start timing. * - ;******************************************************************** +;******************************************************************** +;* Routine called to start timing. * +;******************************************************************** - ZTimerOn proc near +ZTimerOn proc near - ; - ; Save the context of the program being timed. - ; - push ax - pus hf - ; - ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause - ; linear counting rather than count-by-two counting. Also stops - ; timer 0 until the timer count is loaded, except on PS/2 - ; computers. - ; - mov al,00110100b ;mode 2 - out MODE_8253,al - ; - ; 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. - ; - DELAY - subal,al - outTIMER_0_8253,al ;lsb - DELAY - outTIMER_0_8253,al ;msb - ; - ; In case interrupts are disabled, enable interrupts briefly to allow - ; the interrupt generated when switching from mode 3 to mode 2 to be - ; recognized. Interrupts must be enabled for at least 210 ns to allow - ; time for that interrupt to occur. Here, 10 jumps are used for the - ; delay to ensure that the delay time will be more than long enough - ; even on a very fast PC. - ; - pushf - sti - rept 10 - jmp $+2 - endm - MPOPF - ; - ; 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 - ; interrupts in order to avoid getting a half-changed count.) - ; - push ds - subax, ax - movds, ax - movax, ds:[TIMER_COUNT+2] - movcs: [StartBIOSCountHigh],ax - movax, ds:[TIMER_COUNT] - movcs: [StartBIOSCountLow],ax - pop ds - ; - ; Set the timer count to 0 again to start the timing interval. - ; - mov al,00110100b ;set up to load initial - out MODE_8253,al ; timer count - DELAY - subal, al - out TIMER_0_8253,al; load count lsb - DELAY - out TIMER_0_8253,al; load count msb - ; - ; Restore the context of the program being timed and return to it. - ; - MPOPF - popax - ret +; +; Save the context of the program being timed. +; + push ax + pus hf +; +; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause +; linear counting rather than count-by-two counting. Also stops +; timer 0 until the timer count is loaded, except on PS/2 +; computers. +; + mov al,00110100b ;mode 2 + out MODE_8253,al +; +; 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. +; + DELAY + subal,al + outTIMER_0_8253,al ;lsb + DELAY + outTIMER_0_8253,al ;msb +; +; In case interrupts are disabled, enable interrupts briefly to allow +; the interrupt generated when switching from mode 3 to mode 2 to be +; recognized. Interrupts must be enabled for at least 210 ns to allow +; time for that interrupt to occur. Here, 10 jumps are used for the +; delay to ensure that the delay time will be more than long enough +; even on a very fast PC. +; + pushf + sti + rept 10 + jmp $+2 + endm + MPOPF +; +; 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 +; interrupts in order to avoid getting a half-changed count.) +; + push ds + subax, ax + movds, ax + movax, ds:[TIMER_COUNT+2] + movcs: [StartBIOSCountHigh],ax + movax, ds:[TIMER_COUNT] + movcs: [StartBIOSCountLow],ax + pop ds +; +; Set the timer count to 0 again to start the timing interval. +; + mov al,00110100b ;set up to load initial + out MODE_8253,al ; timer count + DELAY + subal, al + out TIMER_0_8253,al; load count lsb + DELAY + out TIMER_0_8253,al; load count msb +; +; Restore the context of the program being timed and return to it. +; + MPOPF + popax + ret - ZTimerOnendp +ZTimerOnendp - ;******************************************************************** - ;* Routine called to stop timing and get count. * - ;******************************************************************** +;******************************************************************** +;* Routine called to stop timing and get count. * +;******************************************************************** - ZTimerOff procnear +ZTimerOff procnear - ; - ; Save the context of the program being timed. - ; - pushf - pushax - pushcx - ; - ; In case interrupts are disabled, enable interrupts briefly to allow - ; any pending timer interrupt to be handled. Interrupts must be - ; enabled for at least 210 ns to allow time for that interrupt to - ; occur. Here, 10 jumps are used for the delay to ensure that the - ; delay time will be more than long enough even on a very fast PC. - ; - sti - rept 10 - jmp $+2 - endm +; +; Save the context of the program being timed. +; + pushf + pushax + pushcx +; +; In case interrupts are disabled, enable interrupts briefly to allow +; any pending timer interrupt to be handled. Interrupts must be +; enabled for at least 210 ns to allow time for that interrupt to +; occur. Here, 10 jumps are used for the delay to ensure that the +; delay time will be more than long enough even on a very fast PC. +; + sti + rept 10 + jmp $+2 + endm - ; - ; Latch the timer count. - ; +; +; Latch the timer count. +; - if PS2 +if PS2 - mov al,00000000b - out MODE_8253,al ;latch timer 0 count - ; - ; 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 - ; 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. - ; + mov al,00000000b + out MODE_8253,al ;latch timer 0 count +; +; 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 +; 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. +; - else +else - ; - ; Set timer 0 to mode 2 (divide-by-N), waiting for a 2-byte count - ; load, which stops timer 0 until the count is loaded. (Only works - ; on fully 8253-compatible chips.) - ; - mov al,00110100b; mode 2 - out MODE_8253,al - DELAY - mov al,00000000b ;latch timer 0 count - out MODE_8253,al +; +; Set timer 0 to mode 2 (divide-by-N), waiting for a 2-byte count +; load, which stops timer 0 until the count is loaded. (Only works +; on fully 8253-compatible chips.) +; + mov al,00110100b; mode 2 + out MODE_8253,al + DELAY + mov al,00000000b ;latch timer 0 count + out MODE_8253,al - endif +endif - cli ;stop the BIOS count - ; - ; Read the BIOS count. (Since interrupts are disabled, the BIOS - ; count won't change.) - ; - push ds - sub ax,ax - mov ds,ax - mov ax,ds:[TIMER_COUNT+2] - mov cs:[EndBIOSCountHigh],ax - mov ax,ds:[TIMER_COUNT] - mov cs:[EndBIOSCountLow],ax - pop ds - ; - ; Read the timer count and save it. - ; - in al,TIMER_0_8253 ;lsb - DELAY - mov ah,al - in al,TIMER_0_8253 ;msb - xchg ah,al - neg ax ;convert from countdown - ; remaining to elapsed - ; count - mov cs:[EndTimedCount],ax - ; - ; Restart timer 0, which is still waiting for an initial count - ; to be loaded. - ; + cli ;stop the BIOS count +; +; Read the BIOS count. (Since interrupts are disabled, the BIOS +; count won't change.) +; + push ds + sub ax,ax + mov ds,ax + mov ax,ds:[TIMER_COUNT+2] + mov cs:[EndBIOSCountHigh],ax + mov ax,ds:[TIMER_COUNT] + mov cs:[EndBIOSCountLow],ax + pop ds +; +; Read the timer count and save it. +; + in al,TIMER_0_8253 ;lsb + DELAY + mov ah,al + in al,TIMER_0_8253 ;msb + xchg ah,al + neg ax ;convert from countdown + ; remaining to elapsed + ; count + mov cs:[EndTimedCount],ax +; +; Restart timer 0, which is still waiting for an initial count +; to be loaded. +; - ife PS2 +ife PS2 - DELAY - mov al,00110100b ;mode 2, waiting to load a - ; 2-byte count - out MODE_8253,al - DELAY - sub al,al - out TIMER_0_8253,al ;lsb - DELAY - mov al,ah - out TIMER_0_8253,al ;msb - DELAY + DELAY + mov al,00110100b ;mode 2, waiting to load a + ; 2-byte count + out MODE_8253,al + DELAY + sub al,al + out TIMER_0_8253,al ;lsb + DELAY + mov al,ah + out TIMER_0_8253,al ;msb + DELAY - endif +endif - sti;let the BIOS count continue - ; - ; Time a zero-length code fragment, to get a reference for how - ; much overhead this routine has. Time it 16 times and average it, - ; for accuracy, rounding the result. - ; - mov cs:[ReferenceCount],0 - mov cx,16 - cli ;interrupts off to allow a - ; precise reference count - RefLoop: - call ReferenceZTimerOn - call ReferenceZTimerOff - loop RefLoop - sti - add cs:[ReferenceCount],8; total + (0.5 * 16) - mov cl,4 - shr cs:[ReferenceCount],cl;(total) / 16 + 0.5 - ; - ; Restore the context of the program being timed and return to it. - ; - popcx - popax - MPOPF - ret +sti;let the BIOS count continue +; +; Time a zero-length code fragment, to get a reference for how +; much overhead this routine has. Time it 16 times and average it, +; for accuracy, rounding the result. +; + mov cs:[ReferenceCount],0 + mov cx,16 + cli ;interrupts off to allow a + ; precise reference count +RefLoop: + call ReferenceZTimerOn + call ReferenceZTimerOff + loop RefLoop + sti + add cs:[ReferenceCount],8; total + (0.5 * 16) + mov cl,4 + shr cs:[ReferenceCount],cl;(total) / 16 + 0.5 +; +; Restore the context of the program being timed and return to it. +; + popcx + popax + MPOPF + ret - ZTimerOff endp +ZTimerOff endp - ; - ; Called by ZTimerOff to start the timer for overhead measurements. - ; +; +; Called by ZTimerOff to start the timer for overhead measurements. +; - ReferenceZTimerOnprocnear - ; - ; Save the context of the program being timed. - ; - pushax - pushf - ; - ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause - ; linear counting rather than count-by-two counting. - ; - mov al,00110100b ;mode 2 - out MODE_8253,al - ; - ; Set the timer count to 0. - ; - DELAY - sub al,al - out TIMER_0_8253,al ;lsb - DELAY - out TIMER_0_8253,al ;msb - ; - ; Restore the context of the program being timed and return to it. - ; - MPOPF - popax - ret +ReferenceZTimerOnprocnear +; +; Save the context of the program being timed. +; + pushax + pushf +; +; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause +; linear counting rather than count-by-two counting. +; + mov al,00110100b ;mode 2 + out MODE_8253,al +; +; Set the timer count to 0. +; + DELAY + sub al,al + out TIMER_0_8253,al ;lsb + DELAY + out TIMER_0_8253,al ;msb +; +; Restore the context of the program being timed and return to it. +; + MPOPF + popax + ret - ReferenceZTimerOnendp +ReferenceZTimerOnendp - ; - ; Called by ZTimerOff to stop the timer and add the result to - ; 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. - ; +; +; Called by ZTimerOff to stop the timer and add the result to +; 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. +; - ReferenceZTimerOff procnear - ; - ; Save the context of the program being timed. - ; - pushf - pushax - pushcx +ReferenceZTimerOff procnear +; +; Save the context of the program being timed. +; + pushf + pushax + pushcx - ; - ; Match the interrupt-window delay in ZTimerOff. - ; - sti - rept10 - jmp$+2 - endm +; +; Match the interrupt-window delay in ZTimerOff. +; + sti + rept10 + jmp$+2 + endm - mov al,00000000b - out MODE_8253,al ;latch timer - ; - ; Read the count and save it. - ; - DELAY - in al,TIMER_0_8253 ;lsb - DELAY - mov ah,al - in al,TIMER_0_8253 ;msb - xchg ah,al - neg ax ;convert from countdown - ; remaining to elapsed - ; count - add cs:[ReferenceCount],ax - ; - ; Restore the context and return. - ; - popcx - popax - MPOPF - ret + mov al,00000000b + out MODE_8253,al ;latch timer +; +; Read the count and save it. +; + DELAY + in al,TIMER_0_8253 ;lsb + DELAY + mov ah,al + in al,TIMER_0_8253 ;msb + xchg ah,al + neg ax ;convert from countdown + ; remaining to elapsed + ; count + add cs:[ReferenceCount],ax +; +; Restore the context and return. +; + popcx + popax + MPOPF + ret - ReferenceZTimerOff endp +ReferenceZTimerOff endp - ;******************************************************************** - ;* Routine called to report timing results. * - ;******************************************************************** +;******************************************************************** +;* Routine called to report timing results. * +;******************************************************************** - ZTimerReportprocnear +ZTimerReportprocnear - pushf - push ax - push bx - push cx - push dx - push si - push di - push ds - ; - push cs ;DOS functions require that DS point - pop ds ; to text to be displayed on the screen - assume ds :Code - ; - ; See if midnight or more than an hour passed during timing. If so, - ; notify the user. - ; - mov ax,[StartBIOSCountHigh] - cmp ax,[EndBIOSCountHigh] - jz CalcBIOSTime ;hour count didn't change, - ; so everything's fine - inc ax - cmp ax,[EndBIOSCountHigh] - jnz TestTooLong ;midnight or two hour - ; boundaries passed, so the - ; results are no good - 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 - ; than an hour + pushf + push ax + push bx + push cx + push dx + push si + push di + push ds + ; + push cs ;DOS functions require that DS point + pop ds ; to text to be displayed on the screen + assume ds :Code +; +; See if midnight or more than an hour passed during timing. If so, +; notify the user. +; + mov ax,[StartBIOSCountHigh] + cmp ax,[EndBIOSCountHigh] + jz CalcBIOSTime ;hour count didn't change, + ; so everything's fine + inc ax + cmp ax,[EndBIOSCountHigh] + jnz TestTooLong ;midnight or two hour + ; boundaries passed, so the + ; results are no good + 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 + ; 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 - ; on the perspicacity of the user to detect that case. - ; - TestTooLong: - mov ah,9 - mov dx,offset TurnOverStr - int 21h - jmp short ZTimerReportDone - ; - ; Convert the BIOS time to microseconds. - ; - CalcBIOSTime: - mov ax,[EndBIOSCountLow] - sub ax,[StartBIOSCountLow] - mov dx,54925 ;number of microseconds each - ; BIOS count represents - mul dx - mov bx,ax ;set aside BIOS count in - mov cx,dx ; microseconds - ; - ; Convert timer count to microseconds. - ; - mov ax,[EndTimedCount] - mov si,8381 - mul si - mov si,10000 - div si ;* .8381 = * 8381 / 10000 - ; - ; Add timer and BIOS counts together to get an overall time in - ; microseconds. - ; - add bx,ax - adc cx,0 - ; - ; Subtract the timer overhead and save the result. - ; - mov ax,[ReferenceCount] - mov si,8381 ;convert the reference count - mul si ; to microseconds - mov si,10000 - div si;* .8381 = * 8381 / 10000 - sub bx,ax - sbb cx,0 - mov [CurrentCountLow],bx - mov [CurrentCountHigh],cx - ; - ; Convert the result to an ASCII string by trial subtractions of - ; powers of 10. - ; - mov di,offset PowersOfTenEnd - offset PowersOfTen - 4 - mov si,offset TimedCountStr - CTSNextDigit: - mov bl,'0' - CTSLoop: - mov ax,[CurrentCountLow] - mov dx,[CurrentCountHigh] - sub ax,PowersOfTen[di] - sbb dx,PowersOfTen[di+2] - jc CTSNextPowerDown - inc bl - mov [CurrentCountLow],ax - mov [CurrentCountHigh],dx - jmp CTSLoop - CTSNextPowerDown: - mov [si],bl - inc si - sub di,4 - jns CTSNextDigit - ; - ; - ; Print the results. - ; - mov ah,9 - mov dx,offset OutputStr - int 21h - ; - ZTimerReportDone: - pop ds - pop di - pop si - pop dx - pop cx - pop bx - pop ax - MPOPF - ret +; +; 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 +; on the perspicacity of the user to detect that case. +; +TestTooLong: + mov ah,9 + mov dx,offset TurnOverStr + int 21h + jmp short ZTimerReportDone +; +; Convert the BIOS time to microseconds. +; +CalcBIOSTime: + mov ax,[EndBIOSCountLow] + sub ax,[StartBIOSCountLow] + mov dx,54925 ;number of microseconds each + ; BIOS count represents + mul dx + mov bx,ax ;set aside BIOS count in + mov cx,dx ; microseconds +; +; Convert timer count to microseconds. +; + mov ax,[EndTimedCount] + mov si,8381 + mul si + mov si,10000 + div si ;* .8381 = * 8381 / 10000 +; +; Add timer and BIOS counts together to get an overall time in +; microseconds. +; + add bx,ax + adc cx,0 +; +; Subtract the timer overhead and save the result. +; + mov ax,[ReferenceCount] + mov si,8381 ;convert the reference count + mul si ; to microseconds + mov si,10000 + div si;* .8381 = * 8381 / 10000 + sub bx,ax + sbb cx,0 + mov [CurrentCountLow],bx + mov [CurrentCountHigh],cx +; +; Convert the result to an ASCII string by trial subtractions of +; powers of 10. +; + mov di,offset PowersOfTenEnd - offset PowersOfTen - 4 + mov si,offset TimedCountStr +CTSNextDigit: + mov bl,'0' +CTSLoop: + mov ax,[CurrentCountLow] + mov dx,[CurrentCountHigh] + sub ax,PowersOfTen[di] + sbb dx,PowersOfTen[di+2] + jc CTSNextPowerDown + inc bl + mov [CurrentCountLow],ax + mov [CurrentCountHigh],dx + jmp CTSLoop +CTSNextPowerDown: + mov [si],bl + inc si + sub di,4 + jns CTSNextDigit +; +; +; Print the results. +; + mov ah,9 + mov dx,offset OutputStr + int 21h +; +ZTimerReportDone: + pop ds + pop di + pop si + pop dx + pop cx + pop bx + pop ax + MPOPF + ret - ZTimerReport endp +ZTimerReport endp - Code ends - end +Code ends + end +``` diff --git a/03-08.md b/03-08.md index 52912cf..db42705 100644 --- a/03-08.md +++ b/03-08.md @@ -70,64 +70,66 @@ timing. **LISTING 3.6 LZTEST.ASM** - ; Program to measure performance of code that takes longer than - ; 54 ms to execute. (LZTEST.ASM) - ; - ; Link with LZTIMER.ASM (Listing 3.5). LZTIME.BAT (Listing 3.7) - ; can be used to assemble and link both files. Code to be - ; measured must be in the file TESTCODE; Listing 3.8 shows - ; a sample file (LST3-8.ASM) which should be named TESTCODE. - ; - ; By Michael Abrash - ; - mystack segment para stack ‘STACK' - db 512 dup(?) - mystack ends - ; - Code segment para public ‘CODE' - assume cs:Code, ds:Code - extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near - Startproc near - push cs - pop ds ;point DS to the code segment, - ; so data as well as code can easily - ; be included in TESTCODE - ; - ; Delay for 6-7 seconds, to let the Enter keystroke that started the - ; program come back up. - ; - mov ah,2ch - int 21h ;get the current time - mov bh,dh ;set the current time aside - DelayLoop: - mov ah,2ch - push bx ;preserve start time - int 21h ;get time - pop bx ;retrieve start time - cmp dh,bh ;is the new seconds count less than - ; the start seconds count? - jnb CheckDelayTime ;no - add dh,60 ;yes, a minute must have turned over, - ; so add one minute - CheckDelayTime: - sub dh,bh ;get time that's passed - cmp dh,7 ;has it been more than 6 seconds yet? - jb DelayLoop ;not yet - ; - include TESTCODE ;code to be measured, including calls - ; to ZTimerOn and ZTimerOff - ; - ; Display the results. - ; - call ZTimerReport - ; - ; Terminate the program. - ; - mov ah,4ch - int 21h - Start endp - Code ends - end Start +```nasm +; Program to measure performance of code that takes longer than +; 54 ms to execute. (LZTEST.ASM) +; +; Link with LZTIMER.ASM (Listing 3.5). LZTIME.BAT (Listing 3.7) +; can be used to assemble and link both files. Code to be +; measured must be in the file TESTCODE; Listing 3.8 shows +; a sample file (LST3-8.ASM) which should be named TESTCODE. +; +; By Michael Abrash +; +mystack segment para stack ‘STACK' + db 512 dup(?) +mystack ends +; +Code segment para public ‘CODE' + assume cs:Code, ds:Code + extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near +Startproc near + push cs + pop ds ;point DS to the code segment, + ; so data as well as code can easily + ; be included in TESTCODE +; +; Delay for 6-7 seconds, to let the Enter keystroke that started the +; program come back up. +; + mov ah,2ch + int 21h ;get the current time + mov bh,dh ;set the current time aside +DelayLoop: + mov ah,2ch + push bx ;preserve start time + int 21h ;get time + pop bx ;retrieve start time + cmp dh,bh ;is the new seconds count less than + ; the start seconds count? + jnb CheckDelayTime ;no + add dh,60 ;yes, a minute must have turned over, + ; so add one minute +CheckDelayTime: + sub dh,bh ;get time that's passed + cmp dh,7 ;has it been more than 6 seconds yet? + jb DelayLoop ;not yet +; + include TESTCODE ;code to be measured, including calls + ; to ZTimerOn and ZTimerOff +; +; Display the results. +; + call ZTimerReport +; +; Terminate the program. +; + mov ah,4ch + int 21h +Start endp +Code ends + end Start +``` As with the precision Zen timer, the program in Listing 3.6 is used by naming the file containing the code to be timed TESTCODE, then diff --git a/03-09.md b/03-09.md index 7d9f889..dd90b81 100644 --- a/03-09.md +++ b/03-09.md @@ -12,67 +12,69 @@ pages: 067-070 **LISTING 3.7 LZTIME.BAT** - echo off - rem - rem *** Listing 3.7 *** - rem - rem *************************************************************** - rem * Batch file LZTIME.BAT, which builds and runs the * - 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 * DOS command: * - rem * * - rem * lztime lst3-8 * - rem * * - rem * Note that MASM and LINK must be in the current directory or * - rem * on the current path in order for this batch file to work. * - rem * * - rem * This batch file can be speeded up by assembling LZTIMER.ASM * - rem * once, then removing the lines: * - rem * * - rem * masm lztimer; * - rem * if errorlevel 1 goto errorend * - rem * * - rem * from this file. * - rem * * - rem * By Michael Abrash * - rem *************************************************************** - rem - rem Make sure a file to test was specified. - rem - if not x%1==x goto ckexist - echo *************************************************************** - echo * Please specify a file to test. * - echo *************************************************************** - goto end - rem - rem Make sure the file exists. - rem - :ckexist - if exist %1 goto docopy - echo *************************************************************** - echo * The specified file, "%1," doesn't exist. * - echo *************************************************************** - goto end - rem - rem copy the file to measure to TESTCODE. - :docopy - copy %1 testcode - masm lztest; - if errorlevel 1 goto errorend - masm lztimer; - if errorlevel 1 goto errorend - link lztest+lztimer; - if errorlevel 1 goto errorend - lztest - goto end - :errorend - echo *************************************************************** - echo * An error occurred while building the long-period Zen timer. * - echo *************************************************************** - :end +```bat +echo off +rem +rem *** Listing 3.7 *** +rem +rem *************************************************************** +rem * Batch file LZTIME.BAT, which builds and runs the * +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 * DOS command: * +rem * * +rem * lztime lst3-8 * +rem * * +rem * Note that MASM and LINK must be in the current directory or * +rem * on the current path in order for this batch file to work. * +rem * * +rem * This batch file can be speeded up by assembling LZTIMER.ASM * +rem * once, then removing the lines: * +rem * * +rem * masm lztimer; * +rem * if errorlevel 1 goto errorend * +rem * * +rem * from this file. * +rem * * +rem * By Michael Abrash * +rem *************************************************************** +rem +rem Make sure a file to test was specified. +rem +if not x%1==x goto ckexist +echo *************************************************************** +echo * Please specify a file to test. * +echo *************************************************************** +goto end +rem +rem Make sure the file exists. +rem +:ckexist +if exist %1 goto docopy +echo *************************************************************** +echo * The specified file, "%1," doesn't exist. * +echo *************************************************************** +goto end +rem +rem copy the file to measure to TESTCODE. +:docopy +copy %1 testcode +masm lztest; +if errorlevel 1 goto errorend +masm lztimer; +if errorlevel 1 goto errorend +link lztest+lztimer; +if errorlevel 1 goto errorend +lztest +goto end +:errorend +echo *************************************************************** +echo * An error occurred while building the long-period Zen timer. * +echo *************************************************************** +:end +``` Listing 3.8 shows sample code that can be timed with the test-bed program of Listing 3.6. Listing 3.8 measures the time required to @@ -81,38 +83,42 @@ the precision Zen timer to handle on the 8088. **LISTING 3.8 LST3-8.ASM** - ; - ; Measures the performance of 20,000 loads of AL from - ; memory. (Use by renaming to TESTCODE, which is - ; included by LZTEST.ASM (Listing 3.6). LZTIME.BAT - ; (Listing 3.7) does this, along with all assembly - ; and linking.) - ; - ; Note: takes about ten minutes to assemble on a slow PC if - ;you are using MASM - ; - jmpSkip;jump around defined data - ; - MemVardb? - ; - Skip: - ; - ; Start timing. - ; - callZTimerOn - ; - rept20000 - moval,[MemVar] - endm - ; - ; Stop timing. - ; - callZTimerOff +```nasm +; +; Measures the performance of 20,000 loads of AL from +; memory. (Use by renaming to TESTCODE, which is +; included by LZTEST.ASM (Listing 3.6). LZTIME.BAT +; (Listing 3.7) does this, along with all assembly +; and linking.) +; +; Note: takes about ten minutes to assemble on a slow PC if +;you are using MASM +; +jmpSkip;jump around defined data +; +MemVardb? +; +Skip: +; +; Start timing. +; +callZTimerOn +; +rept20000 +moval,[MemVar] +endm +; +; Stop timing. +; +callZTimerOff +``` When LZTIME.BAT is run on a PC with the following command line (assuming the code in Listing 3.8 is the file LST3-8.ASM) - lztime lst3-8.asm +```sh +lztime lst3-8.asm +``` the result is 72,544 µs, or about 3.63 µs per load of AL from memory. This is just slightly longer than the time per load of AL measured by @@ -147,4 +153,6 @@ 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 - extern "C" ZTimerOn(void); +```c +extern "C" ZTimerOn(void); +``` diff --git a/03-10.md b/03-10.md index 1fd910e..60a4915 100644 --- a/03-10.md +++ b/03-10.md @@ -16,11 +16,13 @@ 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 timer from C, as, for example, in: - ZTimerOn(): - for (i=0, x=0; i<100; i++) - x += i; - ZTimerOff(); - ZTimerReport(); +```c +ZTimerOn(): +for (i=0, x=0; i<100; i++) + x += i; +ZTimerOff(); +ZTimerReport(); +``` (I'm talking about the precision timer here. The long-period timer—Listing 3.5—requires the same modifications, but to different @@ -46,12 +48,16 @@ One important safety tip when modifying the Zen timer for use with large code model C code: Watch out for optimizing assemblers! TASM actually replaces - call far ptr ReferenceZTimerOn +```nasm +call far ptr ReferenceZTimerOn +``` with - push cs - call near ptr ReferenceZTimerOn +```nasm +push cs +call near ptr ReferenceZTimerOn +``` (and likewise for **ReferenceZTimerOff** ), which works because **ReferenceZTimerOn** is in the same segment as the calling code. This diff --git a/04-02.md b/04-02.md index 338d12e..1d6eef7 100644 --- a/04-02.md +++ b/04-02.md @@ -53,11 +53,15 @@ 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, - mov ax,word ptr [MemVar] +```nasm +mov ax,word ptr [MemVar] +``` takes 4 cycles longer to read the word at address **MemVar** than - mov al,byte ptr [MemVar] +```nasm +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 @@ -72,11 +76,15 @@ prior to adding to it, and one to write the result of the addition back to the destination operand—and thus incurs not one but two 4-cycle penalties. As a result - add word ptr [MemVar],ax +```nasm +add word ptr [MemVar],ax +``` takes about 8 cycles longer to execute than: - add byte ptr [MemVar],al +```nasm +add byte ptr [MemVar],al +``` String instructions can suffer from the 8-bit bus cycle-eater to a greater extent than other instructions. Believe it or not, a single @@ -114,35 +122,39 @@ in all. **LISTING 4.1 LST4-1.ASM** - ; Measures the performance of a loop which uses a - ; byte-sized memory variable as the loop counter. - ; - jmp Skip - ; - Counter db 100 - ; - Skip: - call ZTimerOn - LoopTop: - dec [Counter] - jnz LoopTop - call ZTimerOff +```nasm +; Measures the performance of a loop which uses a +; byte-sized memory variable as the loop counter. +; + jmp Skip +; +Counter db 100 +; +Skip: + call ZTimerOn +LoopTop: + dec [Counter] + jnz LoopTop + call ZTimerOff +``` **LISTING 4.2 LST4-2.ASM** - ; Measures the performance of a loop which uses a - ; word-sized memory variable as the loop counter. - ; - jmp Skip - ; - Counter dw 100 - ; - Skip: - call ZTimerOn - LoopTop: - dec [Counter] - jnz LoopTop - call ZTimerOff +```nasm +; Measures the performance of a loop which uses a +; word-sized memory variable as the loop counter. +; + jmp Skip +; +Counter dw 100 +; +Skip: + call ZTimerOn +LoopTop: + dec [Counter] + 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 diff --git a/04-03.md b/04-03.md index b038832..51cb5ec 100644 --- a/04-03.md +++ b/04-03.md @@ -16,12 +16,16 @@ strive to use byte-sized memory variables whenever possible. That does manipulate a word-sized memory variable in preference to 1 word-sized memory access, as, for instance, - mov dl,byte ptr [MemVar] - mov dh,byte ptr [MemVar+1] +```nasm +mov dl,byte ptr [MemVar] +mov dh,byte ptr [MemVar+1] +``` versus: - mov dx,word ptr [MemVar] +```nasm +mov dx,word ptr [MemVar] +``` 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 @@ -48,25 +52,29 @@ the second byte of each word. **LISTING 4.3 LST4-3.ASM** - ; Measures the performance of reading 1,000 words - ; from memory with 1,000 word-sized accesses. - ; - sub si,si - mov cx,1000 - call ZTimerOn - rep lodsw - call ZTimerOff +```nasm +; Measures the performance of reading 1,000 words +; from memory with 1,000 word-sized accesses. +; + sub si,si + mov cx,1000 + call ZTimerOn + rep lodsw + call ZTimerOff +``` **LISTING 4.4 LST4-4.ASM** - ; Measures the performance of reading 1000 words - ; from memory with 2,000 byte-sized accesses. - ; - sub si,si - mov cx,2000 - call ZTimerOn - rep lodsb - call ZTimerOff +```nasm +; Measures the performance of reading 1000 words +; from memory with 2,000 byte-sized accesses. +; + sub si,si + mov cx,2000 + call ZTimerOn + rep lodsb + call ZTimerOff +``` In short, if you must perform a 16-bit memory access, let the 8088 break the access into two byte-sized accesses for you. The 8088 is more @@ -142,17 +150,21 @@ 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 +```nasm +mov [bx],ax +``` 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 - shr ax,1 - shr ax,1 - shr ax,1 - shr ax,1 - shr ax,1 +```nasm +shr ax,1 +shr ax,1 +shr ax,1 +shr ax,1 +shr ax,1 +``` should execute in 10 cycles, since each **SHR** takes 2 cycles to execute, according to Intel's specifications. Those specifications diff --git a/04-04.md b/04-04.md index c4fe635..8f7c421 100644 --- a/04-04.md +++ b/04-04.md @@ -104,32 +104,36 @@ that the "true" execution time of **SHR** is 8.64 cycles. **LISTING 4.5 LST4-5.ASM** - ; Measures the performance of 1,000 SHR instructions - ; in a row. Since SHR executes in 2 cycles but is - ; 2 bytes long, the prefetch queue is always empty, - ; and prefetching time determines the overall - ; performance of the code. - ; - call ZTimerOn - rept 1000 - shr ax,1 - endm - call ZTimerOff +```nasm +; Measures the performance of 1,000 SHR instructions +; in a row. Since SHR executes in 2 cycles but is +; 2 bytes long, the prefetch queue is always empty, +; and prefetching time determines the overall +; performance of the code. +; + call ZTimerOn + rept 1000 + shr ax,1 + endm + call ZTimerOff +``` **LISTING 4.6 LST4-6.ASM** - ; Measures the performance of 1,000 MUL/SHR instruction - ; pairs in a row. The lengthy execution time of MUL - ; should keep the prefetch queue from ever emptying. - ; - mov cx,1000 - sub ax,ax - call ZTimerOn - rept 1000 - mul ax - shr ax,1 - endm - call ZTimerOff +```nasm +; Measures the performance of 1,000 MUL/SHR instruction +; pairs in a row. The lengthy execution time of MUL +; should keep the prefetch queue from ever emptying. +; + mov cx,1000 + sub ax,ax + call ZTimerOn + rept 1000 + mul ax + shr ax,1 + endm + call ZTimerOff +``` ![**Figure 4.3**  *Execution and instruction prefetching sequence for Listing 4.5.*](images/04-03.jpg) diff --git a/04-05.md b/04-05.md index 0b10db9..83b4f6a 100644 --- a/04-05.md +++ b/04-05.md @@ -43,29 +43,33 @@ Listing 4.6.*](images/04-04.jpg) **LISTING 4.7 LST4-7.ASM** - ; Measures the performance of repeated MOV AL,0 instructions, - ; which take 4 cycles each according to Intel's official - ; specifications. - ; - sub ax,ax - call ZTimerOn - rept 1000 - mov al,0 - endm - call ZTimerOff +```nasm +; Measures the performance of repeated MOV AL,0 instructions, +; which take 4 cycles each according to Intel's official +; specifications. +; + sub ax,ax + call ZTimerOn + rept 1000 + mov al,0 + endm + call ZTimerOff +``` **LISTING 4.8 LST4-8.ASM** - ; Measures the performance of repeated SUB AL,AL instructions, - ; which take 3 cycles each according to Intel's official - ; specifications. - ; - sub ax,ax - call ZTimerOn - rept 1000 - sub al,al - endm - call ZTimerOff +```nasm +; Measures the performance of repeated SUB AL,AL instructions, +; which take 3 cycles each according to Intel's official +; specifications. +; + sub ax,ax + call ZTimerOn + rept 1000 + sub al,al + 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 diff --git a/04-07.md b/04-07.md index cc6b28b..5bf03d2 100644 --- a/04-07.md +++ b/04-07.md @@ -28,17 +28,19 @@ request a memory access from the Bus Interface Unit.) **LISTING 4.9 LST4-9.ASM** - ; Measures the performance of repeated MUL instructions, - ; which allow the prefetch queue to be full at all times, - ; to demonstrate a case in which DRAM refresh has no impact - ; on code performance. - ; - sub ax,ax - call ZTimerOn - rept 1000 - mul ax - endm - call ZTimerOff +```nasm +; Measures the performance of repeated MUL instructions, +; which allow the prefetch queue to be full at all times, +; to demonstrate a case in which DRAM refresh has no impact +; on code performance. +; + sub ax,ax + call ZTimerOn + rept 1000 + mul ax + endm + 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 @@ -56,15 +58,17 @@ to fetch the instruction bytes. **LISTING 4.10 LST4-10.ASM** - ; Measures the performance of repeated SHR instructions, - ; which empty the prefetch queue, to demonstrate the - ; worst-case impact of DRAM refresh on code performance. - ; - call ZTimerOn - rept 1000 - shr ax,1 - endm - call ZTimerOff +```nasm +; Measures the performance of repeated SHR instructions, +; which empty the prefetch queue, to demonstrate the +; worst-case impact of DRAM refresh on code performance. +; + call ZTimerOn + rept 1000 + shr ax,1 + endm + call ZTimerOff +``` 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 diff --git a/04-09.md b/04-09.md index 6127b88..12f41bd 100644 --- a/04-09.md +++ b/04-09.md @@ -86,34 +86,36 @@ long as we had assumed, but a long time nonetheless. **LISTING 4.11 LST4-11.ASM** - ; Times speed of memory access to Enhanced Graphics - ; Adapter graphics mode display memory at A000:0000. - ; - mov ax,0010h - int 10h; select hi-res EGA graphics - ; mode 10 hex (AH=0 selects - ; BIOS set mode function, - ; with AL=mode to select) - ; - mov ax,0a000h - mov ds,ax - mov es,ax ;move to & from same segment - sub si,si ;move to & from same offset - mov di,si - mov cx,800h ;move 2K words - cld - call ZTimerOn - rep movsw ;simply read each of the first - ; 2K words of the destination segment, - ; writing each byte immediately back - ; to the same address. No memory - ; locations are actually altered; this - ; is just to measure memory access - ; times - call ZTimerOff - ; - mov ax,0003h - int 10h ;return to text mode +```nasm +; Times speed of memory access to Enhanced Graphics +; Adapter graphics mode display memory at A000:0000. +; + mov ax,0010h + int 10h; select hi-res EGA graphics + ; mode 10 hex (AH=0 selects + ; BIOS set mode function, + ; with AL=mode to select) +; + mov ax,0a000h + mov ds,ax + mov es,ax ;move to & from same segment + sub si,si ;move to & from same offset + mov di,si + mov cx,800h ;move 2K words + cld + call ZTimerOn + rep movsw ;simply read each of the first + ; 2K words of the destination segment, + ; writing each byte immediately back + ; to the same address. No memory + ; locations are actually altered; this + ; is just to measure memory access + ; times + call ZTimerOff +; + mov ax,0003h + int 10h ;return to text mode +``` 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, @@ -125,24 +127,26 @@ cycle-eater can *more than double* the execution time of 8088 code! **LISTING 4.12 LST4-12.ASM** - ; Times speed of memory access to normal system - ; memory. - ; - mov ax,ds - mov es,ax ;move to & from same segment - sub si,si ;move to & from same offset - mov di,si - mov cx,800h ;move 2K words - cld - call ZTimerOn - rep movsw ;simply read each of the first - ; 2K words of the destination segment, - ; writing each byte immediately back - ; to the same address. No memory - ; locations are actually altered; this - ; is just to measure memory access - ; times - call ZTimerOff +```nasm +; Times speed of memory access to normal system +; memory. +; + mov ax,ds + mov es,ax ;move to & from same segment + sub si,si ;move to & from same offset + mov di,si + mov cx,800h ;move 2K words + cld + call ZTimerOn + rep movsw ;simply read each of the first + ; 2K words of the destination segment, + ; writing each byte immediately back + ; to the same address. No memory + ; locations are actually altered; this + ; is just to measure memory access + ; times + call ZTimerOff +``` 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 diff --git a/05-03.md b/05-03.md index 45f46d8..59c6ea0 100644 --- a/05-03.md +++ b/05-03.md @@ -12,168 +12,170 @@ pages: 118-121 **LISTING 5.1 SEARCH.C** - /* Program to search the file specified by the first command-line - * argument for the string specified by the second command-line - * argument. Performs the search by reading and searching blocks - * of size BLOCK_SIZE. */ +```c +/* Program to search the file specified by the first command-line + * argument for the string specified by the second command-line + * argument. Performs the search by reading and searching blocks + * of size BLOCK_SIZE. */ - #include - #include - #include - #include /* alloc.h for Borland compilers, - malloc.h for Microsoft compilers */ +#include +#include +#include +#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 - that the calling code should already have shortened SearchLength - if necessary to compensate for the distance from the end of the - buffer to the last possible start of a matching sequence in the - buffer. - */ +/* Searches the specified number of sequences in the specified + buffer for matches to SearchString of SearchStringLength. Note + that the calling code should already have shortened SearchLength + if necessary to compensate for the distance from the end of the + buffer to the last possible start of a matching sequence in the + buffer. +*/ - int SearchForString(unsigned char *Buffer, int SearchLength, - unsigned char *SearchString, int SearchStringLength) - { - unsigned char *PotentialMatch; +int SearchForString(unsigned char *Buffer, int SearchLength, + unsigned char *SearchString, int SearchStringLength) +{ + unsigned char *PotentialMatch; - /* Search so long as there are potential-match locations - remaining */ - while ( SearchLength ) { - /* See if the first character of SearchString can be found */ - if ( (PotentialMatch = - memchr(Buffer, *SearchString, SearchLength)) == NULL ) { - break; /* No matches in this buffer */ + /* Search so long as there are potential-match locations + remaining */ + while ( SearchLength ) { + /* See if the first character of SearchString can be found */ + if ( (PotentialMatch = + memchr(Buffer, *SearchString, SearchLength)) == NULL ) { + break; /* No matches in this buffer */ + } + /* The first character matches; see if the rest of the string + also matches */ + if ( SearchStringLength == 1 ) { + return(1); /* That one matching character was the whole + 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 */ } - /* The first character matches; see if the rest of the string - also matches */ - if ( SearchStringLength == 1 ) { - return(1); /* That one matching character was the whole - 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 */ - } - } - /* The string doesn't match; keep going by pointing past the - potential match location we just rejected */ - SearchLength -= PotentialMatch - Buffer + 1; - Buffer = PotentialMatch + 1; - } + } + /* The string doesn't match; keep going by pointing past the + potential match location we just rejected */ + SearchLength -= PotentialMatch - Buffer + 1; + Buffer = PotentialMatch + 1; + } - return(0); /* No match found */ - } + return(0); /* No match found */ +} - main(int argc, char *argv[]) { - int Done; /* Indicates whether search is done */ - int Handle; /* Handle of file being searched */ - int WorkingLength; /* Length of current block */ - int SearchStringLength; /* Length of string to search for */ - int BlockSearchLength; /* Length to search in current block */ - int Found; /* Indicates final search completion - status */ - int NextLoadCount; /* # of bytes to read into next block, - accounting for bytes copied from the - last block */ - unsigned char *WorkingBlock; /* Block storage buffer */ - unsigned char *SearchString; /* Pointer to the string to search for */ - unsigned char *NextLoadPtr; /* Offset at which to start loading - the next block, accounting for - bytes copied from the last block */ +main(int argc, char *argv[]) { + int Done; /* Indicates whether search is done */ + int Handle; /* Handle of file being searched */ + int WorkingLength; /* Length of current block */ + int SearchStringLength; /* Length of string to search for */ + int BlockSearchLength; /* Length to search in current block */ + int Found; /* Indicates final search completion + status */ + int NextLoadCount; /* # of bytes to read into next block, + accounting for bytes copied from the + last block */ + unsigned char *WorkingBlock; /* Block storage buffer */ + unsigned char *SearchString; /* Pointer to the string to search for */ + unsigned char *NextLoadPtr; /* Offset at which to start loading + the next block, accounting for + bytes copied from the last block */ - /* Check for the proper number of arguments */ - if ( argc != 3 ) { - printf("usage: search filename search-string\n"); - exit(1); - } + /* Check for the proper number of arguments */ + if ( argc != 3 ) { + 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]); - exit(1); - } - /* Calculate the length of text to search for */ - SearchString = argv[2]; - 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"); - 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]); + exit(1); + } + /* Calculate the length of text to search for */ + SearchString = argv[2]; + 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"); + exit(1); + } - /* Load the first block at the start of the buffer, and try to - fill the entire buffer */ - NextLoadPtr = WorkingBlock; - NextLoadCount = BLOCK_SIZE; - Done = 0; /* Not done with search yet */ - 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 - (accounting for bytes copied over from the last block), or - 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]); - exit(1); - } - /* 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; - } + /* Load the first block at the start of the buffer, and try to + fill the entire buffer */ + NextLoadPtr = WorkingBlock; + NextLoadCount = BLOCK_SIZE; + Done = 0; /* Not done with search yet */ + 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 + (accounting for bytes copied over from the last block), or + 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]); + exit(1); + } + /* 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; + } - /* Account for any bytes we copied from the end of the last - block in the total length of this block */ - WorkingLength += NextLoadPtr - WorkingBlock; - /* Calculate the number of bytes in this block that could - possibly be the start of a matching sequence that lies - entirely in this block (sequences that run off the end of - the block will be transferred to the next block and found - when that block is searched) - */ - if ( (BlockSearchLength = - 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 - finding a match - */ - } - else { - /* Search this block */ - if ( SearchForString(WorkingBlock, BlockSearchLength, - SearchString, SearchStringLength) ) { - Found = 1; /* We've found a match */ - Done = 1; - } - else { - /* Copy any bytes from the end of the block that start - potentially-matching sequences that would run off - the end of the block over to the next block */ - if ( SearchStringLength > 1 ) { - memcpy(WorkingBlock, - WorkingBlock+BLOCK_SIZE - SearchStringLength + 1, - SearchStringLength - 1); - } - /* Set up to load the next bytes from the file after the - bytes copied from the end of the current block */ - NextLoadPtr = WorkingBlock + SearchStringLength - 1; - NextLoadCount = BLOCK_SIZE - SearchStringLength + 1; - } - } - } while ( !Done ); + /* Account for any bytes we copied from the end of the last + block in the total length of this block */ + WorkingLength += NextLoadPtr - WorkingBlock; + /* Calculate the number of bytes in this block that could + possibly be the start of a matching sequence that lies + entirely in this block (sequences that run off the end of + the block will be transferred to the next block and found + when that block is searched) + */ + if ( (BlockSearchLength = + 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 + finding a match + */ + } + else { + /* Search this block */ + if ( SearchForString(WorkingBlock, BlockSearchLength, + SearchString, SearchStringLength) ) { + Found = 1; /* We've found a match */ + Done = 1; + } + else { + /* Copy any bytes from the end of the block that start + potentially-matching sequences that would run off + the end of the block over to the next block */ + if ( SearchStringLength > 1 ) { + memcpy(WorkingBlock, + WorkingBlock+BLOCK_SIZE - SearchStringLength + 1, + SearchStringLength - 1); + } + /* Set up to load the next bytes from the file after the + bytes copied from the end of the current block */ + NextLoadPtr = WorkingBlock + SearchStringLength - 1; + NextLoadCount = BLOCK_SIZE - SearchStringLength + 1; + } + } + } while ( !Done ); - /* Report the results */ - if ( Found ) { - printf("String found\n"); - } else { - printf("String not found\n"); - } - exit(Found); /* Return the found/not found status as the - DOS errorlevel */ - } + /* Report the results */ + if ( Found ) { + printf("String found\n"); + } else { + printf("String not found\n"); + } + exit(Found); /* Return the found/not found status as the + DOS errorlevel */ +} +``` diff --git a/06-01.md b/06-01.md index 326f71f..1d51f54 100644 --- a/06-01.md +++ b/06-01.md @@ -120,10 +120,14 @@ For example, suppose you have an array base address in BX and an index into the array in SI. You could add the two registers together to address memory, like this: - add bx,si - mov al,[bx] +```nasm +add bx,si +mov al,[bx] +``` Or you could let the processor do the arithmetic for you in a single instruction: - mov al,[bx+si] +```nasm +mov al,[bx+si] +``` diff --git a/06-02.md b/06-02.md index 2696199..cf99bbe 100644 --- a/06-02.md +++ b/06-02.md @@ -19,11 +19,13 @@ 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: - add bx,si - LoopTop: - mov al,[bx] - inc bx - loop LoopTop +```nasm + add bx,si +LoopTop: + mov al,[bx] + inc bx + loop LoopTop +``` Here, **MOV AL,[BX]** is two cycles faster than **MOV AL,[BX+SI]**. @@ -73,24 +75,32 @@ source operands. Imagine that we want to add BX to DI, add two to the result, and store the result in AX. The obvious solution is this: - mov ax,bx - add ax,di - add ax,2 +```nasm +mov ax,bx +add ax,di +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 at the moment.) An elegant alternative solution is simply: - lea ax,[bx+di+2] +```nasm +lea ax,[bx+di+2] +``` Likewise, either of the following would copy SI plus two to DI - mov di,si - add di,2 +```nasm +mov di,si +add di,2 +``` or: - lea di,[si+2] +```nasm +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 @@ -135,14 +145,18 @@ bits, so we can now add up to two 32-bit registers and a constant, *and* shift (or multiply) one of the registers to some extent—all with a single instruction. For example, - lea edi,TableBase[ecx+edx*4] +```nasm +lea edi,TableBase[ecx+edx*4] +``` replaces all this - mov edi,edx - shl edi,2 - add edi,ecx - add edi,offset TableBase +```nasm +mov edi,edx +shl edi,2 +add edi,ecx +add edi,offset TableBase +``` when pointing to an entry in a doubly indexed table. @@ -156,15 +170,19 @@ index on the 386, and can be scaled as the index while being used unchanged as the base. That means that you can, for example, multiply EBX by 5 with: - lea ebx,[ebx+ebx*4] +```nasm +lea ebx,[ebx+ebx*4] +``` Without **LEA** and scaling, multiplication of EBX by 5 would require either a relatively slow **MUL**, along with a set-up instruction or two, or three separate instructions along the lines of the following - mov edx,ebx - shl ebx,2 - add ebx,edx +```nasm +mov edx,ebx +shl ebx,2 +add ebx,edx +``` and would in either case require the destruction of the contents of another register. diff --git a/07-02.md b/07-02.md index c2426f3..3c266d4 100644 --- a/07-02.md +++ b/07-02.md @@ -13,13 +13,17 @@ pages: 139-141 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 +```nasm +and cx,ofh ;Isolate the desired field +jcxz SkipLoop ;If field is 0, don't bother +``` The **AND** instruction has already set the Zero flag, so this - and cx,0fh ;Isolate the desired field - jz SkipLoop ;If field is 0, don't bother +```nasm +and cx,0fh ;Isolate the desired field +jz SkipLoop ;If field is 0, don't bother +``` will do just fine and is faster on all processors. Use **JCXZ** only when the Zero flag isn't already set to reflect the status of CX. diff --git a/07-03.md b/07-03.md index 6115146..ed0ef81 100644 --- a/07-03.md +++ b/07-03.md @@ -12,96 +12,98 @@ pages: 141-143 **LISTING 7.1 L7-1.ASM** - ; Program to illustrate searching through a buffer of a specified - ; length until either a specified byte or a zero byte is - ; encountered. - ; A standard loop terminated with LOOP is used. +```nasm +; Program to illustrate searching through a buffer of a specified +; length until either a specified byte or a zero byte is +; encountered. +; A standard loop terminated with LOOP is used. - .model small - .stack 100h - .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 - SAMPLE_STRING_LENGTH equ $-SampleString + .model small + .stack 100h + .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 +SAMPLE_STRING_LENGTH equ $-SampleString - ; User prompt. - Prompt db ‘Enter character to search for:$' +; User prompt. +Prompt db ‘Enter character to search for:$' - ; Result status messages. - ByteFoundMsg db 0dh,0ah - db ‘Specified byte found.',0dh,0ah,‘$' - ZeroByteFoundMsg db 0dh, 0ah - db ‘Zero byte encountered.',0dh,0ah,‘$' - NoByteFoundMsg db 0dh,0ah - db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$' +; Result status messages. +ByteFoundMsg db 0dh,0ah + db ‘Specified byte found.',0dh,0ah,‘$' +ZeroByteFoundMsg db 0dh, 0ah + db ‘Zero byte encountered.',0dh,0ah,‘$' +NoByteFoundMsg db 0dh,0ah + db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$' - .code - Startprocnear - mov ax,@data ;point to standard data segment - mov ds,ax - mov dx,offset Prompt - mov ah,9 ;DOS print string function - int 21h ;prompt the user - mov ah,1 ;DOS get key function - int 21h ;get the key to search for - mov ah,al ;put character to search for in AH - mov cx,SAMPLE_STRING_LENGTH ;# of bytes to search - mov si,offset SampleString ;point to buffer to search - 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 - ;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 - mov dx,offset ZeroByteFoundMsg ;we found a zero byte - PrintStatus: - mov ah,9 ;DOS print string function - int 21h ;report status - mov ah,4ch ;return to DOS - int 21h - Startendp + .code +Startprocnear + mov ax,@data ;point to standard data segment + mov ds,ax + mov dx,offset Prompt + mov ah,9 ;DOS print string function + int 21h ;prompt the user + mov ah,1 ;DOS get key function + int 21h ;get the key to search for + mov ah,al ;put character to search for in AH + mov cx,SAMPLE_STRING_LENGTH ;# of bytes to search + mov si,offset SampleString ;point to buffer to search + 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 + ;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 + mov dx,offset ZeroByteFoundMsg ;we found a zero byte +PrintStatus: + mov ah,9 ;DOS print string function + int 21h ;report status + mov ah,4ch ;return to DOS + int 21h +Startendp - ; Function to search a buffer of a specified length until either a - ; specified byte or a zero byte is encountered. - ; Input: - ; AH = character to search for - ; CX = maximum length to be searched (must be > 0) - ; DS:SI = pointer to buffer to be searched - ; Output: - ; CX = 0 if and only if we ran out of bytes without finding - ; either the desired byte or a zero byte - ; DS:SI = pointer to searched-for byte if found, otherwise byte - ; after zero byte if found, otherwise byte after last - ; byte checked if neither searched-for byte nor zero - ; byte is found - ; Carry Flag = set if searched-for byte found, reset otherwise +; Function to search a buffer of a specified length until either a +; specified byte or a zero byte is encountered. +; Input: +; AH = character to search for +; CX = maximum length to be searched (must be > 0) +; DS:SI = pointer to buffer to be searched +; Output: +; CX = 0 if and only if we ran out of bytes without finding +; either the desired byte or a zero byte +; DS:SI = pointer to searched-for byte if found, otherwise byte +; after zero byte if found, otherwise byte after last +; byte checked if neither searched-for byte nor zero +; byte is found +; Carry Flag = set if searched-for byte found, reset otherwise - SearchMaxLengthprocnear - cld - SearchMaxLengthLoop: - lodsb ;get the next byte - cmp al,ah ;is this the byte we want? - 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 - ;byte, if any - ByteNotFound: - 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 - ret - SearchMaxLengthendp - end Start +SearchMaxLengthprocnear + cld +SearchMaxLengthLoop: + lodsb ;get the next byte + cmp al,ah ;is this the byte we want? + 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 + ;byte, if any +ByteNotFound: + 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 + ret +SearchMaxLengthendp + end Start +``` ### Unrolling Loops {#Heading7} diff --git a/07-04.md b/07-04.md index f2df2f5..d8ec3ff 100644 --- a/07-04.md +++ b/07-04.md @@ -12,134 +12,136 @@ pages: 143-145 **LISTING 7.2 L7-2.ASM** - ; Program to illustrate searching through a buffer of a specified - ; length until a specified zero byte is encountered. - ; A loop unrolled four times and terminated with LOOP is used. +```nasm +; Program to illustrate searching through a buffer of a specified +; length until a specified zero byte is encountered. +; A loop unrolled four times and terminated with LOOP is used. - .model small - .stack 100h - .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 - SAMPLE_STRING_LENGTH equ $-SampleString + .model small + .stack 100h + .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 +SAMPLE_STRING_LENGTH equ $-SampleString - ; User prompt. - Prompt db ‘Enter character to search for:$' +; User prompt. +Prompt db ‘Enter character to search for:$' - ; Result status messages. - ByteFoundMsg db 0dh,0ah - db ‘Specified byte found.',0dh,0ah,‘$' - ZeroByteFoundMsg db 0dh,0ah - db ‘Zero byte encountered.', 0dh, 0ah, ‘$' - NoByteFoundMsg db 0dh,0ah - db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$' +; Result status messages. +ByteFoundMsg db 0dh,0ah + db ‘Specified byte found.',0dh,0ah,‘$' +ZeroByteFoundMsg db 0dh,0ah + db ‘Zero byte encountered.', 0dh, 0ah, ‘$' +NoByteFoundMsg db 0dh,0ah + db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$' - ; Table of initial, possibly partial loop entry points for - ; SearchMaxLength. - SearchMaxLengthEntryTable labelword - dw SearchMaxLengthEntry4 - dw SearchMaxLengthEntry1 - dw SearchMaxLengthEntry2 - dw SearchMaxLengthEntry3 +; Table of initial, possibly partial loop entry points for +; SearchMaxLength. +SearchMaxLengthEntryTable labelword + dw SearchMaxLengthEntry4 + dw SearchMaxLengthEntry1 + dw SearchMaxLengthEntry2 + dw SearchMaxLengthEntry3 - .code - Start proc near - mov ax,@data ;point to standard data segment - mov ds,ax - mov dx,offset Prompt - mov ah,9 ;DOS print string function - int 21h ;prompt the user - mov ah,1 ;DOS get key function - int 21h ;get the key to search for - mov ah,al ;put character to search for in AH - mov cx,SAMPLE_STRING_LENGTH ;# of bytes to search - mov si,offset SampleString ;point to buffer to search - 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 - ;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 - mov dx,offset ZeroByteFoundMsg ;we found a zero byte - PrintStatus: - mov ah,9 ;DOS print string function - int 21h ;report status + .code +Start proc near + mov ax,@data ;point to standard data segment + mov ds,ax + mov dx,offset Prompt + mov ah,9 ;DOS print string function + int 21h ;prompt the user + mov ah,1 ;DOS get key function + int 21h ;get the key to search for + mov ah,al ;put character to search for in AH + mov cx,SAMPLE_STRING_LENGTH ;# of bytes to search + mov si,offset SampleString ;point to buffer to search + 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 + ;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 + mov dx,offset ZeroByteFoundMsg ;we found a zero byte +PrintStatus: + mov ah,9 ;DOS print string function + int 21h ;report status - mov ah,4ch ;return to DOS - int 21h - Startendp + mov ah,4ch ;return to DOS + int 21h +Startendp - ; Function to search a buffer of a specified length until either a - ; specified byte or a zero byte is encountered. - ; Input: - ; AH = character to search for - ; CX = maximum length to be searched (must be > 0) - ; DS:SI = pointer to buffer to be searched - ; Output: - ; CX = 0 if and only if we ran out of bytes without finding - ; either the desired byte or a zero byte - ; DS:SI = pointer to searched-for byte if found, otherwise byte - ; after zero byte if found, otherwise byte after last - ; byte checked if neither searched-for byte nor zero - ; byte is found - ; Carry Flag = set if searched-for byte found, reset otherwise +; Function to search a buffer of a specified length until either a +; specified byte or a zero byte is encountered. +; Input: +; AH = character to search for +; CX = maximum length to be searched (must be > 0) +; DS:SI = pointer to buffer to be searched +; Output: +; CX = 0 if and only if we ran out of bytes without finding +; either the desired byte or a zero byte +; DS:SI = pointer to searched-for byte if found, otherwise byte +; after zero byte if found, otherwise byte after last +; byte checked if neither searched-for byte nor zero +; byte is found +; Carry Flag = set if searched-for byte found, reset otherwise - SearchMaxLength proc near - cld - mov bx,cx - add cx,3 ;calculate the maximum # of passes - shr cx,1 ;through the loop, which is - shr cx,1 ;unrolled 4 times - and bx,3 ;calculate the index into the entry - ;point table for the first, - ;possibly partial loop - shl bx,1 ;prepare for a word-sized look-up - jmp SearchMaxLengthEntryTable[bx] - ;branch into the unrolled loop to do - ;the first, possibly partial loop - SearchMaxLengthLoop: - SearchMaxLengthEntry4: - lodsb ;get the next byte - cmp al,ah ;is this the byte we want? - 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 - SearchMaxLengthEntry3: - lodsb ;get the next byte - cmp al,ah ;is this the byte we want? - 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 - SearchMaxLengthEntry2: - lodsb ;get the next byte - cmp al,ah ;is this the byte we want? - 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 - SearchMaxLengthEntry1: - lodsb ;get the next byte - cmp al,ah ;is this the byte we want? - 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 - ; four bytes, if any - ByteNotFound: - 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 - ret - SearchMaxLengthendp - end Start +SearchMaxLength proc near + cld + mov bx,cx + add cx,3 ;calculate the maximum # of passes + shr cx,1 ;through the loop, which is + shr cx,1 ;unrolled 4 times + and bx,3 ;calculate the index into the entry + ;point table for the first, + ;possibly partial loop + shl bx,1 ;prepare for a word-sized look-up + jmp SearchMaxLengthEntryTable[bx] + ;branch into the unrolled loop to do + ;the first, possibly partial loop +SearchMaxLengthLoop: +SearchMaxLengthEntry4: + lodsb ;get the next byte + cmp al,ah ;is this the byte we want? + 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 +SearchMaxLengthEntry3: + lodsb ;get the next byte + cmp al,ah ;is this the byte we want? + 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 +SearchMaxLengthEntry2: + lodsb ;get the next byte + cmp al,ah ;is this the byte we want? + 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 +SearchMaxLengthEntry1: + lodsb ;get the next byte + cmp al,ah ;is this the byte we want? + 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 + ; four bytes, if any +ByteNotFound: + 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 + ret +SearchMaxLengthendp + end Start +``` 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 diff --git a/07-05.md b/07-05.md index 80373ab..5be701d 100644 --- a/07-05.md +++ b/07-05.md @@ -19,9 +19,11 @@ 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 position, and OR it with AX, as follows: - MOV BX,1 - SHL BX,CL - OR AX,BX +```nasm +MOV BX,1 +SHL BX,CL +OR AX,BX +``` 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 @@ -31,15 +33,17 @@ bit number in BX, and look the shifted bit up, as shown in Listing 7.3. **LISTING 7.3 L7-3.ASM** - SHL BX,1 ;prepare for word sized look up - OR AX,ShiftTable[BX] ;look up the bit and OR it in - : - ShiftTable LABEL WORD - BIT_PATTERN=0001H - REPT 16 - DW BIT_PATTERN - BIT_PATTERN=BIT_PATTERN SHL 1 - ENDM +```nasm + SHL BX,1 ;prepare for word sized look up + OR AX,ShiftTable[BX] ;look up the bit and OR it in + : +ShiftTable LABEL WORD +BIT_PATTERN=0001H + REPT 16 + DW BIT_PATTERN +BIT_PATTERN=BIT_PATTERN SHL 1 + ENDM +``` Even though it accesses memory, this approach takes only 20 cycles—more than twice as fast as the variable shift. Once again, we were able to @@ -55,14 +59,16 @@ code snippet in Listing 7.4. **LISTING 7.4 L7-4.ASM** - OR EAX,ShiftTable[EBX*4] ;look up the bit and OR it in - : - ShiftTable LABEL DWORD - BIT_PATTERN=0001H - REPT 32 - DD BIT_PATTERN - BIT_PATTERN=BIT_PATTERN SHL 1 - ENDM +```nasm + OR EAX,ShiftTable[EBX*4] ;look up the bit and OR it in + : +ShiftTable LABEL DWORD +BIT_PATTERN=0001H + REPT 32 + DD BIT_PATTERN +BIT_PATTERN=BIT_PATTERN SHL 1 + ENDM +``` > ![](images/i.jpg) > Besides illustrating the advantages of local optimization, this example @@ -119,30 +125,34 @@ addition to the next. **LISTING 7.5 L7-5.ASM** - CLC ;clear the Carry for the initial addition - LOOP_TOP: - MOV AX,[SI];get next source operand word - ADC [DI],AX;add with Carry to dest operand word - INC SI ;point to next source operand word - INC SI - INC DI ;point to next dest operand word - INC DI - LOOP LOOP_TOP +```nasm + CLC ;clear the Carry for the initial addition +LOOP_TOP: + MOV AX,[SI];get next source operand word + ADC [DI],AX;add with Carry to dest operand word + INC SI ;point to next source operand word + INC SI + INC DI ;point to next dest operand word + INC DI + LOOP LOOP_TOP +``` If **ADD** were used, the Carry flag would have to be saved between additions, with code along the lines shown in Listing 7.6. **LISTING 7.6 L7-6.ASM** - CLC ;clear the carry for the initial addition - LOOP_TOP: - MOV AX,[SI] ;get next source operand word - ADC [DI],AX ;add with carry to dest operand word - LAHF ;set aside the carry flag - ADD SI,2 ;point to next source operand word - ADD DI,2 ;point to next dest operand word - SAHF ;restore the carry flag - LOOP LOOP_TOP +```nasm + CLC ;clear the carry for the initial addition +LOOP_TOP: + MOV AX,[SI] ;get next source operand word + ADC [DI],AX ;add with carry to dest operand word + LAHF ;set aside the carry flag + ADD SI,2 ;point to next source operand word + ADD DI,2 ;point to next dest operand word + SAHF ;restore the carry flag + LOOP LOOP_TOP +``` 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 @@ -182,13 +192,17 @@ 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 Carry flag. For instance - INC AX - ADC DX,0 +```nasm +INC AX +ADC DX,0 +``` does *not* increment the 32-bit value in DX:AX. To do that, you'd need the following: - ADD AX,1 - ADC DX,0 +```nasm +ADD AX,1 +ADC DX,0 +``` As always, pay attention! \ No newline at end of file diff --git a/08-02.md b/08-02.md index a4d7451..fc4c351 100644 --- a/08-02.md +++ b/08-02.md @@ -91,8 +91,10 @@ 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 +```nasm +repnz scasw +jz Match +``` It's a simple example—but, I hope, a convincing one. Stretch your brain when you optimize. diff --git a/08-03.md b/08-03.md index 2c28656..aee9f56 100644 --- a/08-03.md +++ b/08-03.md @@ -12,125 +12,127 @@ pages: 156-160 **LISTING 8.1 L8-1.C** - /* Program to search an array spanning a linked list of variable- - sized blocks, for all entries with a specified ID number, - and return the average of the values of all such entries. Each of - the variable-sized blocks may contain any number of data entries, - stored as an array of structures within the block. */ +```c +/* Program to search an array spanning a linked list of variable- + sized blocks, for all entries with a specified ID number, + and return the average of the values of all such entries. Each of + the variable-sized blocks may contain any number of data entries, + stored as an array of structures within the block. */ - #include - #ifdef __TURBOC__ - #include - #else - #include - #endif +#include +#ifdef __TURBOC__ +#include +#else +#include +#endif - void main(void); - void exit(int); - unsigned int FindIDAverage(unsigned int, struct BlockHeader *); - /* Structure that starts each variable-sized block */ - struct BlockHeader { - struct BlockHeader *NextBlock; /* Pointer to next block, or NULL - if this is the last block in the - linked list */ - unsigned int BlockCount; /* The number of DataElement entries - in this variable-sized block */ - }; +void main(void); +void exit(int); +unsigned int FindIDAverage(unsigned int, struct BlockHeader *); +/* Structure that starts each variable-sized block */ +struct BlockHeader { + struct BlockHeader *NextBlock; /* Pointer to next block, or NULL + if this is the last block in the + linked list */ + unsigned int BlockCount; /* The number of DataElement entries + in this variable-sized block */ +}; - /* 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 */ - }; +/* 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 */ +}; - void main(void) { - int i,j; - unsigned int IDToFind; - struct BlockHeader *BaseArrayBlockPointer,*WorkingBlockPointer; - struct DataElement *WorkingDataPointer; - struct BlockHeader **LastBlockPointer; +void main(void) { + int i,j; + unsigned int IDToFind; + struct BlockHeader *BaseArrayBlockPointer,*WorkingBlockPointer; + struct DataElement *WorkingDataPointer; + struct BlockHeader **LastBlockPointer; - 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; - /* Create 5 blocks of varying sizes */ - for (i = 1; i < 6; i++) { - /* Try to get memory for the next block */ - if ((WorkingBlockPointer = - (struct BlockHeader *) malloc(sizeof(struct BlockHeader) + - sizeof(struct DataElement) * i * 10)) == NULL) { - exit(1); - } - /* Set the # of data elements in this block */ - WorkingBlockPointer->BlockCount = i * 10; - /* Link the new block into the chain */ - *LastBlockPointer = WorkingBlockPointer; - /* Point to the first data field */ - WorkingDataPointer = - (struct DataElement *) ((char *)WorkingBlockPointer + - sizeof(struct BlockHeader)); - /* Fill the data fields with ID numbers and values */ - for (j = 0; j < (i * 10); j++, WorkingDataPointer++) { - WorkingDataPointer->ID = j; - WorkingDataPointer->Value = i * 1000 + j; - } - /* 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 - that there are no more blocks */ - WorkingBlockPointer->NextBlock = NULL; - printf("Average of all elements with ID %d: %u\n", - IDToFind, FindIDAverage(IDToFind, BaseArrayBlockPointer)); - exit(0); - } + 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; + /* Create 5 blocks of varying sizes */ + for (i = 1; i < 6; i++) { + /* Try to get memory for the next block */ + if ((WorkingBlockPointer = + (struct BlockHeader *) malloc(sizeof(struct BlockHeader) + + sizeof(struct DataElement) * i * 10)) == NULL) { + exit(1); + } + /* Set the # of data elements in this block */ + WorkingBlockPointer->BlockCount = i * 10; + /* Link the new block into the chain */ + *LastBlockPointer = WorkingBlockPointer; + /* Point to the first data field */ + WorkingDataPointer = + (struct DataElement *) ((char *)WorkingBlockPointer + + sizeof(struct BlockHeader)); + /* Fill the data fields with ID numbers and values */ + for (j = 0; j < (i * 10); j++, WorkingDataPointer++) { + WorkingDataPointer->ID = j; + WorkingDataPointer->Value = i * 1000 + j; + } + /* 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 + that there are no more blocks */ + WorkingBlockPointer->NextBlock = NULL; + printf("Average of all elements with ID %d: %u\n", + IDToFind, FindIDAverage(IDToFind, BaseArrayBlockPointer)); + exit(0); +} - /* Searches through the array of DataElement entries spanning the - linked list of variable-sized blocks, starting with the block - pointed to by BlockPointer, for all entries with IDs matching - SearchedForID, and returns the average value of those entries. If - no matches are found, zero is returned */ +/* Searches through the array of DataElement entries spanning the + linked list of variable-sized blocks, starting with the block + pointed to by BlockPointer, for all entries with IDs matching + SearchedForID, and returns the average value of those entries. If + no matches are found, zero is returned */ - unsigned int FindIDAverage(unsigned int SearchedForID, - struct BlockHeader *BlockPointer) - { - struct DataElement *DataPointer; - unsigned int IDMatchSum; - unsigned int IDMatchCount; - unsigned int WorkingBlockCount; +unsigned int FindIDAverage(unsigned int SearchedForID, + struct BlockHeader *BlockPointer) +{ + struct DataElement *DataPointer; + unsigned int IDMatchSum; + unsigned int IDMatchCount; + unsigned int WorkingBlockCount; - IDMatchCount = IDMatchSum = 0; - /* Search through all the linked blocks until the last block - (marked with a NULL pointer to the next block) has been - searched */ - do { - /* Point to the first DataElement entry within this block */ - DataPointer = - (struct DataElement *) ((char *)BlockPointer + - sizeof(struct BlockHeader)); - /* Search all the DataElement entries within this block - and accumulate data from all that match the desired ID */ - for (WorkingBlockCount=0; - WorkingBlockCountBlockCount; - WorkingBlockCount++, DataPointer++) { - /* If the ID matches, add in the value and increment the - match counter */ - if (DataPointer->ID == SearchedForID) { - IDMatchCount++; - IDMatchSum += DataPointer->Value; - } - } - /* Point to the next block, and continue as long as that pointer - isn't NULL */ - } while ((BlockPointer = BlockPointer->NextBlock) != NULL); - /* Calculate the average of all matches */ - if (IDMatchCount == 0) - return(0); /* Avoid division by 0 */ - else - return(IDMatchSum / IDMatchCount); - } + IDMatchCount = IDMatchSum = 0; + /* Search through all the linked blocks until the last block + (marked with a NULL pointer to the next block) has been + searched */ + do { + /* Point to the first DataElement entry within this block */ + DataPointer = + (struct DataElement *) ((char *)BlockPointer + + sizeof(struct BlockHeader)); + /* Search all the DataElement entries within this block + and accumulate data from all that match the desired ID */ + for (WorkingBlockCount=0; + WorkingBlockCountBlockCount; + WorkingBlockCount++, DataPointer++) { + /* If the ID matches, add in the value and increment the + match counter */ + if (DataPointer->ID == SearchedForID) { + IDMatchCount++; + IDMatchSum += DataPointer->Value; + } + } + /* Point to the next block, and continue as long as that pointer + isn't NULL */ + } while ((BlockPointer = BlockPointer->NextBlock) != NULL); + /* Calculate the average of all matches */ + if (IDMatchCount == 0) + return(0); /* Avoid division by 0 */ + else + return(IDMatchSum / IDMatchCount); +} +``` The main body of Listing 8.1 constructs a linked list of memory blocks of various sizes and stores an array of structures across those blocks, @@ -154,32 +156,34 @@ instruction can be used. **LISTING 8.2 L8-2.COD** - ; Code generated by Microsoft C for inner loop of FindIDAverage. - ;|*** for (WorkingBlockCount=0; - ;|*** WorkingBlockCountBlockCount; - ;|*** WorkingBlockCount++, DataPointer++) { - mov WORD PTR [bp-6],0 ;WorkingBlockCount - mov bx,WORD PTR [bp+6] ;BlockPointer - cmp WORD PTR [bx+2],0 - je $FB264 - mov cx,WORD PTR [bx+2] - add WORD PTR [bp-6],cx ;WorkingBlockCount - mov di,WORD PTR [bp-2] ;IDMatchSum - mov dx,WORD PTR [bp-4] ;IDMatchCount - $L20004: - ;|*** if (DataPointer->ID == SearchedForID) { - mov ax,WORD PTR [si] - cmp WORD PTR [bp+4],ax ;SearchedForID - jne $I265 - ;|*** IDMatchCount++; - inc dx - ;|*** IDMatchSum += DataPointer->Value; - add di,WORD PTR [si+2] - ;|*** } - ;|*** } - $I265: - add si,4 - loop $L20004 - mov WORD PTR [bp-2],di ;IDMatchSum - mov WORD PTR [bp-4],dx ;IDMatchCount - $FB264: +```nasm +; Code generated by Microsoft C for inner loop of FindIDAverage. +;|*** for (WorkingBlockCount=0; +;|*** WorkingBlockCountBlockCount; +;|*** WorkingBlockCount++, DataPointer++) { + mov WORD PTR [bp-6],0 ;WorkingBlockCount + mov bx,WORD PTR [bp+6] ;BlockPointer + cmp WORD PTR [bx+2],0 + je $FB264 + mov cx,WORD PTR [bx+2] + add WORD PTR [bp-6],cx ;WorkingBlockCount + mov di,WORD PTR [bp-2] ;IDMatchSum + mov dx,WORD PTR [bp-4] ;IDMatchCount +$L20004: +;|*** if (DataPointer->ID == SearchedForID) { + mov ax,WORD PTR [si] + cmp WORD PTR [bp+4],ax ;SearchedForID + jne $I265 +;|*** IDMatchCount++; + inc dx +;|*** IDMatchSum += DataPointer->Value; + add di,WORD PTR [si+2] +;|*** } +;|*** } +$I265: + add si,4 + loop $L20004 + mov WORD PTR [bp-2],di ;IDMatchSum + mov WORD PTR [bp-4],dx ;IDMatchCount +$FB264: +``` diff --git a/08-04.md b/08-04.md index 90dc95b..18aca33 100644 --- a/08-04.md +++ b/08-04.md @@ -24,19 +24,21 @@ optimization, isn't it? **LISTING 8.3 L8-3.ASM** - ; Typically optimized assembly language version of FindIDAverage. - SearchedForID equ 4 ;Passed parameter offsets in the - BlockPointer equ 6 ; stack frame (skip over pushed BP - ; and the return address) - NextBlock equ 0 ;Field offsets in struct BlockHeader - BlockCount equ 2 - BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader - ID equ 0 ;struct DataElement field offsets - Value equ 2 - DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement - .model small - .code - public _FindIDAverage +```nasm +; Typically optimized assembly language version of FindIDAverage. +SearchedForID equ 4 ;Passed parameter offsets in the +BlockPointer equ 6 ; stack frame (skip over pushed BP +; and the return address) +NextBlock equ 0 ;Field offsets in struct BlockHeader +BlockCount equ 2 +BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader +ID equ 0 ;struct DataElement field offsets +Value equ 2 +DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement + .model small + .code + public _FindIDAverage +``` | | On 20 MHz 386 | On 10 MHz 286 | |------------------------------------------------------------|------------------|------------------| @@ -47,49 +49,51 @@ optimization, isn't it? Table: Table 8.1 Execution Times of FindIDAverage. - _FindIDAverage proc near - 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 - ; Search through all the linked blocks until the last block - ; (marked with a NULL pointer to the next block) has been searched. - BlockLoop: - ; Point to the first DataElement entry within this block. - lea di,[si+BLOCK_HEADER_SIZE] - ; 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 ;No data in this block - IntraBlockLoop: - cmp [di+ID],ax ;Do we have an ID match? - jnz NoMatch ;No match - inc bx ;We have a match; IDMatchCount++; - add dx,[di+Value] ;IDMatchSum += DataPointer->Value; - 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. - DoNextBlock: - mov si,[si+NextBlock] ;Get pointer to the next block - and si,si ;Is it a NULL pointer? - jnz BlockLoop ;No, continue - ; 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 - 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 - ret - _FindIDAverage ENDP - end +```nasm +_FindIDAverage proc near + 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 +; Search through all the linked blocks until the last block +; (marked with a NULL pointer to the next block) has been searched. +BlockLoop: +; Point to the first DataElement entry within this block. + lea di,[si+BLOCK_HEADER_SIZE] +; 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 ;No data in this block +IntraBlockLoop: + cmp [di+ID],ax ;Do we have an ID match? + jnz NoMatch ;No match + inc bx ;We have a match; IDMatchCount++; + add dx,[di+Value] ;IDMatchSum += DataPointer->Value; +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. +DoNextBlock: + mov si,[si+NextBlock] ;Get pointer to the next block + and si,si ;Is it a NULL pointer? + jnz BlockLoop ;No, continue +; 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 + 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 + ret +_FindIDAverage ENDP + end +``` Listing 8.4 tosses some sophisticated optimization techniques into the mix. The loop is unrolled eight times, eliminating a good deal of @@ -102,91 +106,93 @@ but not a tremendous return for the optimization effort invested. **LISTING 8.4 L8-4.ASM** - ; Heavily optimized assembly language version of FindIDAverage. - ; Features an unrolled loop and more efficient pointer use. - SearchedForID equ 4 ;Passed parameter offsets in the - BlockPointer equ 6 ; stack frame (skip over pushed BP - ; and the return address) - NextBlock equ 0 ;Field offsets in struct BlockHeader - BlockCount equ 2 - BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader - ID equ 0 ;struct DataElement field offsets - Value equ 2 - DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement - .model small - .code - public _FindIDAverage - _FindIDAverage proc near - push bp ;Save caller's stack frame - mov bp,sp ;Point to our stack frame - push di ;Preserve C register variables - push si - mov di,ds ;Prepare for SCASW - mov es,di - cld - 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 - ; Search through all of the linked blocks until the last block - ; (marked with a NULL pointer to the next block) has been searched. - BlockLoop: - ; Point to the first DataElement entry within this block. - lea di,[si+BLOCK_HEADER_SIZE] - ; 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 - mov bp,cx ;***stack frame no longer available*** - add cx,7 - shr cx,1 ;Number of repetitions of the unrolled - shr cx,1 ; loop = (BlockCount + 7) / 8 - shr cx,1 - and bp,7 ;Generate the entry point for the - shl bp,1 ; first, possibly partial pass through - jmp cs:[LoopEntryTable+bp] ; the unrolled loop and - ; vector to that entry point - align 2 - LoopEntryTable label word - dw LoopEntry8,LoopEntry1,LoopEntry2,LoopEntry3 - dw LoopEntry4,LoopEntry5,LoopEntry6,LoopEntry7 - M_IBL macro P1 - local NoMatch - LoopEntry&P1&: - scasw ;Do we have an ID match? - jnz NoMatch ;No match - ;We have a match - inc bx ;IDMatchCount++; - add dx,[di] ;IDMatchSum += DataPointer->Value; - NoMatch: - add di,DATA_ELEMENT_SIZE-2 ;point to the next element - ; (SCASW advanced 2 bytes already) - endm - align 2 - IntraBlockLoop: - M_IBL 8 - M_IBL 7 - M_IBL 6 - M_IBL 5 - M_IBL 4 - M_IBL 3 - M_IBL 2 - M_IBL 1 - loop IntraBlockLoop - ; 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? - jnz BlockLoop ;No, continue - ; 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 - 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 - ret - _FindIDAverage ENDP - end +```nasm +; Heavily optimized assembly language version of FindIDAverage. +; Features an unrolled loop and more efficient pointer use. +SearchedForID equ 4 ;Passed parameter offsets in the +BlockPointer equ 6 ; stack frame (skip over pushed BP + ; and the return address) +NextBlock equ 0 ;Field offsets in struct BlockHeader +BlockCount equ 2 +BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader +ID equ 0 ;struct DataElement field offsets +Value equ 2 +DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement + .model small + .code + public _FindIDAverage +_FindIDAverage proc near + push bp ;Save caller's stack frame + mov bp,sp ;Point to our stack frame + push di ;Preserve C register variables + push si + mov di,ds ;Prepare for SCASW + mov es,di + cld + 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 +; Search through all of the linked blocks until the last block +; (marked with a NULL pointer to the next block) has been searched. +BlockLoop: +; Point to the first DataElement entry within this block. + lea di,[si+BLOCK_HEADER_SIZE] +; 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 + mov bp,cx ;***stack frame no longer available*** + add cx,7 + shr cx,1 ;Number of repetitions of the unrolled + shr cx,1 ; loop = (BlockCount + 7) / 8 + shr cx,1 + and bp,7 ;Generate the entry point for the + shl bp,1 ; first, possibly partial pass through + jmp cs:[LoopEntryTable+bp] ; the unrolled loop and + ; vector to that entry point + align 2 +LoopEntryTable label word + dw LoopEntry8,LoopEntry1,LoopEntry2,LoopEntry3 + dw LoopEntry4,LoopEntry5,LoopEntry6,LoopEntry7 +M_IBL macro P1 + local NoMatch +LoopEntry&P1&: + scasw ;Do we have an ID match? + jnz NoMatch ;No match + ;We have a match + inc bx ;IDMatchCount++; + add dx,[di] ;IDMatchSum += DataPointer->Value; +NoMatch: + add di,DATA_ELEMENT_SIZE-2 ;point to the next element + ; (SCASW advanced 2 bytes already) + endm + align 2 +IntraBlockLoop: + M_IBL 8 + M_IBL 7 + M_IBL 6 + M_IBL 5 + M_IBL 4 + M_IBL 3 + M_IBL 2 + M_IBL 1 + loop IntraBlockLoop +; 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? + jnz BlockLoop ;No, continue +; 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 + 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 + ret +_FindIDAverage ENDP + end +``` \ No newline at end of file diff --git a/08-05.md b/08-05.md index 5e5593e..0aa1980 100644 --- a/08-05.md +++ b/08-05.md @@ -20,149 +20,155 @@ merely rearranged. **LISTING 8.5 L8-5.C** - /* Program to search an array spanning a linked list of variable- - sized blocks, for all entries with a specified ID number, - and return the average of the values of all such entries. Each of - the variable-sized blocks may contain any number of data entries, - stored in the form of two separate arrays, one for ID numbers and - one for values. */ +```c +/* Program to search an array spanning a linked list of variable- + sized blocks, for all entries with a specified ID number, + and return the average of the values of all such entries. Each of + the variable-sized blocks may contain any number of data entries, + stored in the form of two separate arrays, one for ID numbers and + one for values. */ - #include - #ifdef __TURBOC__ - #include - #else - #include - #endif +#include +#ifdef __TURBOC__ +#include +#else +#include +#endif - void main(void); - void exit(int); - extern unsigned int FindIDAverage2(unsigned int, - struct BlockHeader *); +void main(void); +void exit(int); +extern unsigned int FindIDAverage2(unsigned int, + struct BlockHeader *); +``` ![**Figure 8.3**  *Linked array storage format (version 2).*](images/08-03.jpg) - /* Structure that starts each variable-sized block */ - struct BlockHeader { - struct BlockHeader *NextBlock; /* Pointer to next block, or NULL - if this is the last block in the - linked list */ - unsigned int BlockCount; /* The number of DataElement entries - in this variable-sized block */ - }; +```c +/* Structure that starts each variable-sized block */ +struct BlockHeader { + struct BlockHeader *NextBlock; /* Pointer to next block, or NULL + if this is the last block in the + linked list */ + unsigned int BlockCount; /* The number of DataElement entries + in this variable-sized block */ +}; - void main(void) { - int i,j; - unsigned int IDToFind; - struct BlockHeader *BaseArrayBlockPointer,*WorkingBlockPointer; - int *WorkingDataPointer; - struct BlockHeader **LastBlockPointer; +void main(void) { + int i,j; + unsigned int IDToFind; + struct BlockHeader *BaseArrayBlockPointer,*WorkingBlockPointer; + 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 */ - LastBlockPointer = &BaseArrayBlockPointer; - /* Create 5 blocks of varying sizes */ - for (i = 1; i < 6; i++) { - /* Try to get memory for the next block */ - if ((WorkingBlockPointer = - (struct BlockHeader *) malloc(sizeof(struct BlockHeader) + - sizeof(int) * 2 * i * 10)) == NULL) { - exit(1); - } - /* Set the number of data elements in this block */ - WorkingBlockPointer->BlockCount = i * 10; - /* Link the new block into the chain */ - *LastBlockPointer = WorkingBlockPointer; - /* Point to the first data field */ - WorkingDataPointer = (int *) ((char *)WorkingBlockPointer + - sizeof(struct BlockHeader)); - /* Fill the data fields with ID numbers and values */ - for (j = 0; j < (i * 10); j++, WorkingDataPointer++) { - *WorkingDataPointer = j; - *(WorkingDataPointer + i * 10) = i * 1000 + j; - } - /* 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 - that there are no more blocks */ - WorkingBlockPointer->NextBlock = NULL; - printf("Average of all elements with ID %d: %u\n", - IDToFind, FindIDAverage2(IDToFind, BaseArrayBlockPointer)); - exit(0); - } + /* Build an array across 5 blocks, for testing */ + /* Anchor the linked list to BaseArrayBlockPointer */ + LastBlockPointer = &BaseArrayBlockPointer; + /* Create 5 blocks of varying sizes */ + for (i = 1; i < 6; i++) { + /* Try to get memory for the next block */ + if ((WorkingBlockPointer = + (struct BlockHeader *) malloc(sizeof(struct BlockHeader) + + sizeof(int) * 2 * i * 10)) == NULL) { + exit(1); + } + /* Set the number of data elements in this block */ + WorkingBlockPointer->BlockCount = i * 10; + /* Link the new block into the chain */ + *LastBlockPointer = WorkingBlockPointer; + /* Point to the first data field */ + WorkingDataPointer = (int *) ((char *)WorkingBlockPointer + + sizeof(struct BlockHeader)); + /* Fill the data fields with ID numbers and values */ + for (j = 0; j < (i * 10); j++, WorkingDataPointer++) { + *WorkingDataPointer = j; + *(WorkingDataPointer + i * 10) = i * 1000 + j; + } + /* 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 + that there are no more blocks */ + WorkingBlockPointer->NextBlock = NULL; + printf("Average of all elements with ID %d: %u\n", + IDToFind, FindIDAverage2(IDToFind, BaseArrayBlockPointer)); + exit(0); +} +``` **LISTING 8.6 L8-6.ASM** - ; Alternative optimized assembly language version of FindIDAverage - ; requires data organized as two arrays within each block rather - ; than as an array of two-value element structures. This allows the - ; use of REP SCASW for ID searching. +```asm +; Alternative optimized assembly language version of FindIDAverage +; requires data organized as two arrays within each block rather +; than as an array of two-value element structures. This allows the +; use of REP SCASW for ID searching. - SearchedForIDequ4 ;Passed parameter offsets in the - BlockPointerequ6 ; stack frame (skip over pushed BP - ; and the return address) - NextBlockequ0 ;Field offsets in struct BlockHeader - BlockCountequ2 - BLOCK_HEADER_SIZEequ4 ;Number of bytes in struct BlockHeader +SearchedForIDequ4 ;Passed parameter offsets in the +BlockPointerequ6 ; stack frame (skip over pushed BP + ; and the return address) +NextBlockequ0 ;Field offsets in struct BlockHeader +BlockCountequ2 +BLOCK_HEADER_SIZEequ4 ;Number of bytes in struct BlockHeader - .model small - .code - public _FindIDAverage2 - _FindIDAverage2 proc near - push bp ;Save caller's stack frame - mov bp,sp ;Point to our stack frame - push di ;Preserve C register variables - push si - mov di,ds ;Prepare for SCASW - mov es,di - cld - mov si,[bp+BlockPointer] ;Pointer to first block - mov ax,[bp+SearchedForID] ;ID we're looking for - sub dx,dx ;IDMatchSum = 0 - mov bp,dx ;IDMatchCount = 0 - ;***stack frame no longer available*** - ; Search through all the linked blocks until the last block - ; (marked with a NULL pointer to the next block) has been searched. - BlockLoop: - ; 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 - ; to search through - 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) - ; Point to the first DataElement entry within this block. - lea di,[si+BLOCK_HEADER_SIZE] - IntraBlockLoop: - repnz scasw ;Search for the ID - jnz DoNextBlock ;No match, the block is done - inc bp ;We have a match; IDMatchCount++; - add dx,[di+bx-2];IDMatchSum += DataPointer->Value; - ; (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. - DoNextBlock: - mov si,[si+NextBlock] ;Get pointer to the next block - and si,si ;Is it a NULL pointer? - jnz BlockLoop ;No, continue - ; 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 - 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 - ret - _FindIDAverage2 ENDP - end + .model small + .code + public _FindIDAverage2 +_FindIDAverage2 proc near + push bp ;Save caller's stack frame + mov bp,sp ;Point to our stack frame + push di ;Preserve C register variables + push si + mov di,ds ;Prepare for SCASW + mov es,di + cld + mov si,[bp+BlockPointer] ;Pointer to first block + mov ax,[bp+SearchedForID] ;ID we're looking for + sub dx,dx ;IDMatchSum = 0 + mov bp,dx ;IDMatchCount = 0 + ;***stack frame no longer available*** +; Search through all the linked blocks until the last block +; (marked with a NULL pointer to the next block) has been searched. +BlockLoop: +; 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 + ; to search through + 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) +; Point to the first DataElement entry within this block. + lea di,[si+BLOCK_HEADER_SIZE] +IntraBlockLoop: + repnz scasw ;Search for the ID + jnz DoNextBlock ;No match, the block is done + inc bp ;We have a match; IDMatchCount++; + add dx,[di+bx-2];IDMatchSum += DataPointer->Value; +; (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. +DoNextBlock: + mov si,[si+NextBlock] ;Get pointer to the next block + and si,si ;Is it a NULL pointer? + jnz BlockLoop ;No, continue +; 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 + 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 + 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 diff --git a/09-01.md b/09-01.md index e8362a5..1cd833d 100644 --- a/09-01.md +++ b/09-01.md @@ -88,13 +88,17 @@ machine word (32 bits in 386 protected mode, 16 bits otherwise), but it renders **LEA** useless for multiword operations, which use the Carry flag to tie together partial results. For example, these instructions - ADD EAX,EBX - ADC EDX,ECX +```nasm +ADD EAX,EBX +ADC EDX,ECX +``` could *not* be replaced - LEA EAX,[EAX+EBX] - ADC EDX,ECX +```nasm +LEA EAX,[EAX+EBX] +ADC EDX,ECX +``` because **LEA** doesn't affect the Carry flag. @@ -103,16 +107,18 @@ performing pointer arithmetic, however. For instance, the following code uses **LEA** to advance the pointers while adding one 128-bit memory variable to another such variable: - MOV ECX,4 ;# of 32-bit words to add - CLC - ;no carry into the initial ADC - ADDLOOP: +```nasm + MOV ECX,4 ;# of 32-bit words to add + CLC +;no carry into the initial ADC +ADDLOOP: - 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 - LOOP ADDLOOP + 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 + LOOP ADDLOOP +``` (Yes, I could use **LODSD** instead of **MOV/LEA**; I'm just illustrating a point here. Besides, **LODS** is only 1 cycle faster than @@ -140,9 +146,11 @@ of my favorites: John's code for setting AX to its absolute value is: - CWD - XOR AX,DX - SUB AX,DX +```nasm +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 @@ -150,10 +158,12 @@ 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: - AND AX,AX ;negative? - JNS IsPositive ;no - NEG AX ;yes,negate it - IsPositive: +```nasm + AND AX,AX ;negative? + JNS IsPositive ;no + 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 @@ -162,19 +172,23 @@ optimization world unto itself. 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 - REP MOVSW ;copy as many words as possible - ADC CX,CX ;CX=1 if copy length was odd, - ;0 else - REP MOVSB ;copy any odd byte +```nasm +SHR CX,1 ;word count +REP MOVSW ;copy as many words as possible +ADC CX,CX ;CX=1 if copy length was odd, + ;0 else +REP MOVSB ;copy any odd byte +``` (**ADC CX,CX** can be replaced with **RCL CX,1**; which is faster depends on the processor type.) It might be hard to believe that the above is faster than this: - SHR CX,1 ;word count - REP MOVSW ;copy as many words as - ;possible - JNC CopyDone ;done if even copy length - MOVSB ;copy the odd byte - CopyDone: +```nasm + SHR CX,1 ;word count + REP MOVSW ;copy as many words as + ;possible + JNC CopyDone ;done if even copy length + MOVSB ;copy the odd byte +CopyDone: +``` diff --git a/09-02.md b/09-02.md index 6963a62..728c30d 100644 --- a/09-02.md +++ b/09-02.md @@ -27,34 +27,42 @@ video programmers are undoubtedly familiar with the following code to multiply AX times 80 (the width in bytes of the bitmap in most PC display modes): - SHL AX,1 ;*2 - SH LAX,1 ;*4 - SH LAX,1 ;*8 - SH LAX,1 ;*16 - MO VBX,AX - SH LAX,1 ;*32 - SH LAX,1 ;*64 - ADD AX,BX ;*80 +```nasm +SHL AX,1 ;*2 +SH LAX,1 ;*4 +SH LAX,1 ;*8 +SH LAX,1 ;*16 +MO VBX,AX +SH LAX,1 ;*32 +SH LAX,1 ;*64 +ADD AX,BX ;*80 +``` Using **LEA** on the 386, the above could be reduced to - LEA EAX,[EAX*2] ;*2 - LEA EAX,[EAX*8] ;*16 - LEA EAX,[EAX+EAX*4] ;*80 +```nasm +LEA EAX,[EAX*2] ;*2 +LEA EAX,[EAX*8] ;*16 +LEA EAX,[EAX+EAX*4] ;*80 +``` which still isn't as fast as using a lookup table like - MOV EAX,MultiplesOf80Table[EAX*4] +```nasm +MOV EAX,MultiplesOf80Table[EAX*4] +``` but is close and takes a great deal less space. Of course, on the 386, the shift and add version could also be reduced to this considerably more efficient code: - SH LAX,4 ;*16 - MOV BX,AX - SHL AX,2 ;*64 - ADD AX,BX ;*80 +```nasm +SH LAX,4 ;*16 +MOV BX,AX +SHL AX,2 ;*64 +ADD AX,BX ;*80 +``` #### Speeding Up Multiplication {#Heading5} diff --git a/09-03.md b/09-03.md index 0bbbd19..1311b6d 100644 --- a/09-03.md +++ b/09-03.md @@ -40,94 +40,96 @@ using **REPZ CMPS** to check scanning matches. **LISTING 9.1 L9-1.ASM** - ; Searches a text buffer for a text string. Uses REPNZ SCASB to sca"n - ; the buffer for locations that match the first character of the - ; searched-for string, then uses REPZ CMPS to check fully only those - ; locations that REPNZ SCASB has identified as potential matches. - ; - ; Adapted from Zen of Assembly Language, by Michael Abrash - ; - ; C small model-callable as: - ; unsigned char * FindString(unsigned char * Buffer, - ; unsigned int BufferLength, unsigned char * SearchString, - ; unsigned int SearchStringLength); - ; - ; Returns a pointer to the first match for SearchString in Buffer,or - ; a NULL pointer if no match is found. Buffer should not start at - ; offset 0 in the data segment to avoid confusing a match at 0 with - ; no match found. - Parmsstruc - dw 2 dup(?) ;pushed BP/return address - Buffer dw ? ;pointer to buffer to search - BufferLength dw ? ;length of buffer to search - SearchString dw ? ;pointer to string for which to search - SearchStringLength dw ? ;length of string for which to search - Parmsends - .model small - .code - public _FindString - _FindStringprocnear - push bp ;preserve caller's stack frame - mov bp,sp ;point to our stack frame - 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 - mov bx,[bp+SearchStringLength] ;length of string - and bx,bx - jz FindStringNotFound ;no match if string is 0 length - movd x,[bp+BufferLength] ;length of buffer - sub dx,bx ;difference between buffer and string lengths - jc FindStringNotFound ;no match if search string is - ; longer than buffer - inc dx ;difference between buffer and search string - ; lengths, plus 1 (# of possible string start - ; locations to check in the buffer) - mov di,ds - mov es,di - 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 - 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 - ;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 - mov dx,cx ;set aside the remaining length to search in - ; the buffer - mov si,bp ;point to the rest of the search string - mov cx,bx ;string length (minus first byte) - 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 - FindStringWord: - 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 - repz cmpsw ;check the rest of the string a word at a time - 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? - jnz FindStringLoop ;yes-check next byte - FindStringNotFound: - sub ax,ax ;return a NULL pointer indicating that the - jmp FindStringDone ; string was not found - FindStringFound: - pop ax ;point to the buffer location at which the - dec ax ; string was found (earlier we pushed the - ; address of the byte after the start of the - ; potential match) - FindStringDone: - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - _FindStringendp - end +```nasm +; Searches a text buffer for a text string. Uses REPNZ SCASB to sca"n +; the buffer for locations that match the first character of the +; searched-for string, then uses REPZ CMPS to check fully only those +; locations that REPNZ SCASB has identified as potential matches. +; +; Adapted from Zen of Assembly Language, by Michael Abrash +; +; C small model-callable as: +; unsigned char * FindString(unsigned char * Buffer, +; unsigned int BufferLength, unsigned char * SearchString, +; unsigned int SearchStringLength); +; +; Returns a pointer to the first match for SearchString in Buffer,or +; a NULL pointer if no match is found. Buffer should not start at +; offset 0 in the data segment to avoid confusing a match at 0 with +; no match found. +Parmsstruc + dw 2 dup(?) ;pushed BP/return address +Buffer dw ? ;pointer to buffer to search +BufferLength dw ? ;length of buffer to search +SearchString dw ? ;pointer to string for which to search +SearchStringLength dw ? ;length of string for which to search +Parmsends + .model small + .code + public _FindString +_FindStringprocnear + push bp ;preserve caller's stack frame + mov bp,sp ;point to our stack frame + 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 + mov bx,[bp+SearchStringLength] ;length of string + and bx,bx + jz FindStringNotFound ;no match if string is 0 length + movd x,[bp+BufferLength] ;length of buffer + sub dx,bx ;difference between buffer and string lengths + jc FindStringNotFound ;no match if search string is + ; longer than buffer + inc dx ;difference between buffer and search string + ; lengths, plus 1 (# of possible string start + ; locations to check in the buffer) + mov di,ds + mov es,di + 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 +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 + ;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 + mov dx,cx ;set aside the remaining length to search in + ; the buffer + mov si,bp ;point to the rest of the search string + mov cx,bx ;string length (minus first byte) + 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 +FindStringWord: + 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 + repz cmpsw ;check the rest of the string a word at a time + 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? + jnz FindStringLoop ;yes-check next byte +FindStringNotFound: + sub ax,ax ;return a NULL pointer indicating that the + jmp FindStringDone ; string was not found +FindStringFound: + pop ax ;point to the buffer location at which the + dec ax ; string was found (earlier we pushed the + ; address of the byte after the start of the + ; potential match) +FindStringDone: + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame +ret +_FindStringendp + end +``` diff --git a/09-04.md b/09-04.md index b340e47..40b9528 100644 --- a/09-04.md +++ b/09-04.md @@ -12,134 +12,138 @@ pages: 178-180 **LISTING 9.2 L9-2.ASM** - ; Searches a text buffer for a text string. Uses REPNZ SCASB to scan - ; the buffer for locations that match a specified character of the - ; searched-for string, then uses REPZ CMPS to check fully only those - ; locations that REPNZ SCASB has identified as potential matches. - ; - ; C small model-callable as: - ; unsigned char * FindString(unsigned char * Buffer, - ; unsigned int BufferLength, unsigned char * SearchString, - ; unsigned int SearchStringLength, - ; unsigned int ScanCharOffset); - ; - ; Returns a pointer to the first match for SearchString in Buffer,or - ; a NULL pointer if no match is found. Buffer should not start at - ; offset 0 in the data segment to avoid confusing a match at 0 with - ; no match found. - Parms struc - dw 2 dup(?) ;pushed BP/return address - Buffer dw ? ;pointer to buffer to search - BufferLength dw ? ;length of buffer to search - SearchString dw ? ;pointer to string for which to search - SearchStringLength dw ? ;length of string for which to search - ScanCharOffset dw ? ;offset in string of character for - ; which to scan - Parmsends - .model small - .code - public _FindString - _FindStringprocnear - push bp ;preserve caller's stack frame - mov bp,sp ;point to our stack frame - 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 - mov cx,[bp+SearchStringLength] ;length of string - jcxz FindStringNotFound ;no match if string is 0 length - mov dx,[bp+BufferLength] ;length of buffer - sub dx,cx ;difference between buffer and search - ; lengths - jc FindStringNotFound ;no match if search string is - ; longer than buffer - inc dx ; difference between buffer and search string - ; lengths, plus 1 (# of possible string start - ; locations to check in the buffer) - mov di,ds - mov es,di - mov di,[bp+Buffer] ;point ES:DI to buffer to search thru - mov bx,[bp+ScanCharOffset] ;offset in string of character - ; on which to scan - add di,bx ;point ES:DI to first buffer byte to scan - mov al,[si+bx] ;put the scan character in AL - inc bx ;set BX to the offset back to the start of the - ; potential full match after a scan match, - ; accounting for the 1-byte overrun of - ; REPNZ SCASB - 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 - ;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 - mov dx,cx ;set aside the remaining length to search in - ; the buffer - sub di,bx ;point back to the potential start of the - ; match in the buffer - mov si,[bp+SearchString] ;point to the start of the string - mov cx,[bp+SearchStringLength] ;string length - 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 - FindStringWord: - jcxz FindStringFound ;if the string is only 1 byte long, - ; we've found a match - repz cmpsw ;check the rest of the string a word at a time - 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? - jnz FindStringLoop ;yes-check next byte - FindStringNotFound: - sub ax,ax ;return a NULL pointer indicating that the - jmp FindStringDone ; string was not found - FindStringFound: - pop ax ;point to the buffer location at which the - 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 si - pop bp ;restore caller's stack frame - ret - _FindStringendp - end +```nasm +; Searches a text buffer for a text string. Uses REPNZ SCASB to scan +; the buffer for locations that match a specified character of the +; searched-for string, then uses REPZ CMPS to check fully only those +; locations that REPNZ SCASB has identified as potential matches. +; +; C small model-callable as: +; unsigned char * FindString(unsigned char * Buffer, +; unsigned int BufferLength, unsigned char * SearchString, +; unsigned int SearchStringLength, +; unsigned int ScanCharOffset); +; +; Returns a pointer to the first match for SearchString in Buffer,or +; a NULL pointer if no match is found. Buffer should not start at +; offset 0 in the data segment to avoid confusing a match at 0 with +; no match found. +Parms struc + dw 2 dup(?) ;pushed BP/return address +Buffer dw ? ;pointer to buffer to search +BufferLength dw ? ;length of buffer to search +SearchString dw ? ;pointer to string for which to search +SearchStringLength dw ? ;length of string for which to search +ScanCharOffset dw ? ;offset in string of character for + ; which to scan +Parmsends + .model small + .code + public _FindString +_FindStringprocnear + push bp ;preserve caller's stack frame + mov bp,sp ;point to our stack frame + 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 + mov cx,[bp+SearchStringLength] ;length of string + jcxz FindStringNotFound ;no match if string is 0 length + mov dx,[bp+BufferLength] ;length of buffer + sub dx,cx ;difference between buffer and search + ; lengths + jc FindStringNotFound ;no match if search string is + ; longer than buffer + inc dx ; difference between buffer and search string + ; lengths, plus 1 (# of possible string start + ; locations to check in the buffer) + mov di,ds + mov es,di + mov di,[bp+Buffer] ;point ES:DI to buffer to search thru + mov bx,[bp+ScanCharOffset] ;offset in string of character + ; on which to scan + add di,bx ;point ES:DI to first buffer byte to scan + mov al,[si+bx] ;put the scan character in AL + inc bx ;set BX to the offset back to the start of the + ; potential full match after a scan match, + ; accounting for the 1-byte overrun of + ; REPNZ SCASB +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 + ;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 + mov dx,cx ;set aside the remaining length to search in + ; the buffer + sub di,bx ;point back to the potential start of the + ; match in the buffer + mov si,[bp+SearchString] ;point to the start of the string + mov cx,[bp+SearchStringLength] ;string length + 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 +FindStringWord: + jcxz FindStringFound ;if the string is only 1 byte long, + ; we've found a match + repz cmpsw ;check the rest of the string a word at a time + 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? + jnz FindStringLoop ;yes-check next byte +FindStringNotFound: + sub ax,ax ;return a NULL pointer indicating that the + jmp FindStringDone ; string was not found +FindStringFound: + pop ax ;point to the buffer location at which the + 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 si + pop bp ;restore caller's stack frame + ret +_FindStringendp + end +``` **LISTING 9.3 L9-3.C** - /* Program to exercise buffer-search routines in Listings 9.1 & 9.2 */ - #include - #include +```c +/* Program to exercise buffer-search routines in Listings 9.1 & 9.2 */ +#include +#include - #define DISPLAY_LENGTH 40 - 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 \ - 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..."; - void main() { - 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); - } else { - /* TestString was found. Zero-terminate TempBuffer; strncpy - 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", - TestString, DISPLAY_LENGTH, - strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH)); - } - } +#define DISPLAY_LENGTH 40 +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 \ +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..."; +void main() { + 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); + } else { + /* TestString was found. Zero-terminate TempBuffer; strncpy + 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", + TestString, DISPLAY_LENGTH, + strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH)); + } +} +``` diff --git a/09-05.md b/09-05.md index 97645ef..4ffe34d 100644 --- a/09-05.md +++ b/09-05.md @@ -43,38 +43,39 @@ one. **LISTING 9.4 L9-4.ASM** - . - ;-------------------------------------------------------------------------- - ; Sorts an array of ints. C callable (small model). 25 bytes. - ; void sort( int num, int a[] ); - ; - ; Courtesy of David Stafford. - ;-------------------------------------------------------------------------- +```nasm +;-------------------------------------------------------------------------- +; Sorts an array of ints. C callable (small model). 25 bytes. +; void sort( int num, int a[] ); +; +; Courtesy of David Stafford. +;-------------------------------------------------------------------------- - .model small - .code - public _sort + .model small + .code + public _sort - top: mov dx,[bx] ;swap two adjacent integers - xchg dx,[bx+2] - xchg dx,[bx] - cmp dx,[bx] ;did we put them in the right order? - jl top ;no, swap them back - inc bx ;go to next integer - inc bx - loop top - _sort: pop dx ;get return address (entry point) - pop cx ;get count - pop bx ;get pointer - push bx ;restore pointer - dec cx ;decrement count - push cx ;save count - push dx ;restore return address - jg top ;if cx > 0 +top: mov dx,[bx] ;swap two adjacent integers + xchg dx,[bx+2] + xchg dx,[bx] + cmp dx,[bx] ;did we put them in the right order? + jl top ;no, swap them back + inc bx ;go to next integer + inc bx + loop top +_sort: pop dx ;get return address (entry point) + pop cx ;get count + pop bx ;get pointer + push bx ;restore pointer + dec cx ;decrement count + push cx ;save count + push dx ;restore return address + jg top ;if cx > 0 - ret + ret - end + end +``` #### Full 32-Bit Division {#Heading8} diff --git a/09-06.md b/09-06.md index 1d09873..25dbc5e 100644 --- a/09-06.md +++ b/09-06.md @@ -12,91 +12,95 @@ pages: 182-185 **LISTING 9.5 L9-5.ASM** - ; Divides an arbitrarily long unsigned dividend by a 16-bit unsigned - ; divisor. C near-callable as: - ; unsigned int Div(unsigned int * Dividend, - ; int DividendLength, unsigned int Divisor, - ; unsigned int * Quotient); - ; - ; Returns the remainder of the division. - ; - ; Tested with TASM 2. +```nasm +; Divides an arbitrarily long unsigned dividend by a 16-bit unsigned +; divisor. C near-callable as: +; unsigned int Div(unsigned int * Dividend, +; int DividendLength, unsigned int Divisor, +; unsigned int * Quotient); +; +; Returns the remainder of the division. +; +; Tested with TASM 2. - parms struc - dw 2 dup (?) ;pushed BP & return address - Dividend dw ? ;pointer to value to divide, stored in Intel - ; order, with lsb at lowest address, msb at - ; highest. Must be composed of an integral - ; number of words - DividendLength dw ? ;# of bytes in Dividend. Must be a multiple - ; of 2 - Divisor dw ? ;value by which to divide. Must not be zero, - ; or a Divide By Zero interrupt will occur - Quotient dw ? ;pointer to buffer in which to store the - ; result of the division, in Intel order. - ; The quotient returned is of the same - ; length as the dividend - parmsends +parms struc + dw 2 dup (?) ;pushed BP & return address +Dividend dw ? ;pointer to value to divide, stored in Intel + ; order, with lsb at lowest address, msb at + ; highest. Must be composed of an integral + ; number of words +DividendLength dw ? ;# of bytes in Dividend. Must be a multiple + ; of 2 +Divisor dw ? ;value by which to divide. Must not be zero, + ; or a Divide By Zero interrupt will occur +Quotient dw ? ;pointer to buffer in which to store the + ; result of the division, in Intel order. + ; The quotient returned is of the same + ; length as the dividend +parmsends - .model small - .code - public _Div - _Divprocnear - push bp ;preserve caller's stack frame - mov bp,sp ;point to our stack frame - push si ;preserve caller's register variables - push di + .model small + .code + public _Div +_Divprocnear + push bp ;preserve caller's stack frame + mov bp,sp ;point to our stack frame + push si ;preserve caller's register variables + push di - std ;we're working from msb to lsb - mov ax,ds - mov es,ax ;for STOS - mov cx,[bp+DividendLength] - sub cx,2 - mov si,[bp+Dividend] - add si,cx ;point to the last word of the dividend - ; (the most significant word) - mov di,[bp+Quotient] - add di,cx ;point to the last word of the quotient - ; buffer (the most significant word) - mov bx,[bp+Divisor] - shr cx,1 - inc cx ;# of words to process - sub dx,dx ;convert initial divisor word to a 32-bit - ;value for DIV - DivLoop: - lod sw ;get next most significant word of divisor - div bx - sto sw ;save this word of the quotient - ;DX contains the remainder at this point, - ; ready to prepend to the next divisor word - loop DivLoop - mov ax,dx ;return the remainder - cld ;restore default Direction flag setting - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - _Divendp - end + std ;we're working from msb to lsb + mov ax,ds + mov es,ax ;for STOS + mov cx,[bp+DividendLength] + sub cx,2 + mov si,[bp+Dividend] + add si,cx ;point to the last word of the dividend + ; (the most significant word) + mov di,[bp+Quotient] + add di,cx ;point to the last word of the quotient + ; buffer (the most significant word) + mov bx,[bp+Divisor] + shr cx,1 + inc cx ;# of words to process + sub dx,dx ;convert initial divisor word to a 32-bit + ;value for DIV +DivLoop: + lod sw ;get next most significant word of divisor + div bx + sto sw ;save this word of the quotient + ;DX contains the remainder at this point, + ; ready to prepend to the next divisor word + loop DivLoop + mov ax,dx ;return the remainder + cld ;restore default Direction flag setting + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame + ret +_Divendp + end +``` **LISTING 9.6 L9-6.C** - /* Sample use of Div function to perform division when the result - doesn't fit in 16 bits */ +```c +/* Sample use of Div function to perform division when the result + doesn't fit in 16 bits */ - #include +#include - extern unsigned int Div(unsigned int * Dividend, - int DividendLength, unsigned int Divisor, - unsigned int * Quotient); +extern unsigned int Div(unsigned int * Dividend, + int DividendLength, unsigned int Divisor, + unsigned int * Quotient); - main() { - unsigned long m, i = 0x20000001; - unsigned int k, j = 0x10; +main() { + unsigned long m, i = 0x20000001; + 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); - } + k = Div((unsigned int *)&i, sizeof(i), j, (unsigned int *)&m); + printf("%lu / %u = %lu r %u\n", i, j, m, k); +} +``` #### Sweet Spot Revisited {#Heading9} diff --git a/09-07.md b/09-07.md index cae3b2f..c970aa5 100644 --- a/09-07.md +++ b/09-07.md @@ -52,11 +52,13 @@ 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: - Ptr dd ? - : - mov word ptr [Ptr],5 - mov word ptr [Ptr+2],1000h - jmp [Ptr] +```nasm +Ptr dd ? + : + mov word ptr [Ptr],5 + mov word ptr [Ptr+2],1000h + jmp [Ptr] +``` That will work, but at a price in performance. On an 8088, **JMP DWORD PTR [*mem*]** (an indirect far jump) takes at least 37 cycles; **JMP @@ -87,22 +89,24 @@ 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 *** - ; *** to build a direct far jump to an absolute address *** - ; - ; Tested with TASM 2 and MASM 5. +```nasm +; Program to perform a direct far jump to address 1000:5. +; *** 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. - FarSeg segment at 01000h - org 5 - FarLabel label far - FarSeg ends +FarSeg segment at 01000h + org 5 +FarLabel label far +FarSeg ends - .model small - .code - start: - jmp FarLabel - end start + .model small + .code +start: + 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: @@ -116,12 +120,16 @@ something that does—a rule with plenty of history on its side. To finish up this chapter, consider these two items. First, in 32-bit protected mode, - sub eax,eax - inc eax +```nasm +sub eax,eax +inc eax +``` takes 4 cycles to execute, but is only 3 bytes long, while - mov eax,1 +```nasm +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). @@ -129,11 +137,15 @@ Both code fragments are ways to set **EAX** to 1 (although the first affects the flags and the second doesn't); this is a classic trade-off of speed for space. Second, - or ebx,-1 +```nasm +or ebx,-1 +``` takes 2 cycles to execute and is 3 bytes long, while - move bx,-1 +```nasm +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 diff --git a/10-02.md b/10-02.md index a470090..26d9658 100644 --- a/10-02.md +++ b/10-02.md @@ -64,28 +64,30 @@ default optimization was used. All times measured with the Zen timer **LISTING 10.1 L10-1.C** - /* Finds and returns the greatest common divisor of two positive - integers. Works by trying every integral divisor between the - smaller of the two integers and 1, until a divisor that divides - both integers evenly is found. All C code tested with Microsoft - and Borland compilers.*/ +```c +/* Finds and returns the greatest common divisor of two positive + integers. Works by trying every integral divisor between the + smaller of the two integers and 1, until a divisor that divides + both integers evenly is found. All C code tested with Microsoft + and Borland compilers.*/ - unsigned int gcd(unsigned int int1, unsigned int int2) { - unsigned int temp, trial_divisor; - /* Swap if necessary to make sure that int1 >= int2 */ - if (int1 < int2) { - temp = int1; - int1 = int2; - int2 = temp; - } - /* Now just try every divisor from int2 on down, until a common - divisor is found. This can never be an infinite loop because - 1 divides everything evenly */ - for (trial_divisor = int2; ((int1 % trial_divisor) != 0) || - ((int2 % trial_divisor) != 0); trial_divisor—) - ; - return(trial_divisor); - } +unsigned int gcd(unsigned int int1, unsigned int int2) { + unsigned int temp, trial_divisor; + /* Swap if necessary to make sure that int1 >= int2 */ + if (int1 < int2) { + temp = int1; + int1 = int2; + int2 = temp; + } + /* Now just try every divisor from int2 on down, until a common + divisor is found. This can never be an infinite loop because + 1 divides everything evenly */ + for (trial_divisor = int2; ((int1 % trial_divisor) != 0) || + ((int2 % trial_divisor) != 0); trial_divisor—) + ; + return(trial_divisor); +} +``` #### Wasted Breakthroughs {#Heading5} @@ -99,32 +101,34 @@ in Listing 10.2. **LISTING 10.2 L10-2.C** - /* 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 - the two, in which case the two integers swap roles and the - subtraction process continues. */ +```c +/* 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 + 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 - done */ - if (int1 == int2) { - return(int1); - } - /* Swap if necessary to make sure that int1 >= int2 */ - if (int1 < int2) { - temp = int1; - int1 = int2; - int2 = temp; - } +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 + done */ + if (int1 == int2) { + return(int1); + } + /* Swap if necessary to make sure that int1 >= int2 */ + if (int1 < int2) { + temp = int1; + int1 = int2; + int2 = temp; + } - /* Subtract int2 from int1 until int1 is no longer the larger of - the two */ - do { - int1 -= int2; - } while (int1 > int2); - /* Now recursively call this function to continue the process */ - return(gcd(int1, int2)); - } + /* Subtract int2 from int1 until int1 is no longer the larger of + the two */ + do { + int1 -= int2; + } while (int1 > int2); + /* Now recursively call this function to continue the process */ + return(gcd(int1, int2)); +} +``` diff --git a/10-03.md b/10-03.md index 52c1a55..4571879 100644 --- a/10-03.md +++ b/10-03.md @@ -45,49 +45,51 @@ 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 - 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 - repeated. */ +```c +/* Finds and returns the greatest common divisor of two integers. + 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 + repeated. */ - static unsigned int gcd_recurs(unsigned int, unsigned int); +static unsigned int gcd_recurs(unsigned int, unsigned int); - 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 - done */ - if (int1 == int2) { - return(int1); - } - /* Swap if necessary to make sure that int1 >= int2 */ - if (int1 < int2) { - temp = int1; - int1 = int2; - int2 = temp; - } +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 + done */ + if (int1 == int2) { + return(int1); + } + /* Swap if necessary to make sure that int1 >= int2 */ + if (int1 < int2) { + temp = int1; + int1 = int2; + int2 = temp; + } - /* Now call the recursive form of the function, which assumes - that the first parameter is the larger of the two */ - return(gcd_recurs(int1, int2)); - } + /* Now call the recursive form of the function, which assumes + that the first parameter is the larger of the two */ + return(gcd_recurs(int1, int2)); +} - static unsigned int gcd_recurs(unsigned int larger_int, - unsigned int smaller_int) - { - int temp; +static unsigned int gcd_recurs(unsigned int larger_int, + unsigned int smaller_int) +{ + int temp; - /* If the remainder of larger_int divided by smaller_int is 0, - then smaller_int is the gcd */ - if ((temp = larger_int % smaller_int) == 0) { - return(smaller_int); - } - /* Make smaller_int the larger integer and the remainder the - smaller integer, and call this function recursively to - continue the process */ - return(gcd_recurs(smaller_int, temp)); - } + /* If the remainder of larger_int divided by smaller_int is 0, + then smaller_int is the gcd */ + if ((temp = larger_int % smaller_int) == 0) { + return(smaller_int); + } + /* Make smaller_int the larger integer and the remainder the + smaller integer, and call this function recursively to + continue the process */ + return(gcd_recurs(smaller_int, temp)); +} +``` 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 @@ -129,38 +131,40 @@ 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 - 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 - repeated. Avoids code recursion. */ +```c +/* Finds and returns the greatest common divisor of two integers. + 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 + repeated. Avoids code recursion. */ - unsigned int gcd(unsigned int int1, unsigned int int2) { - unsigned int temp; +unsigned int gcd(unsigned int int1, unsigned int int2) { + unsigned int temp; - /* Swap if necessary to make sure that int1 >= int2 */ - if (int1 < int2) { - temp = int1; - int1 = int2; - 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 - 0, assign int2 to int1, and the remainder to int2, then - repeat */ - for (;;) { - /* If the remainder of int1 divided by int2 is 0, then int2 is - the gcd */ - if ((temp = int1 % int2) == 0) { - return(int2); - } - /* Make int2 the larger integer and the remainder the - smaller integer, and repeat the process */ - int1 = int2; - int2 = temp; - } - } + /* Swap if necessary to make sure that int1 >= int2 */ + if (int1 < int2) { + temp = int1; + int1 = int2; + 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 + 0, assign int2 to int1, and the remainder to int2, then + repeat */ + for (;;) { + /* If the remainder of int1 divided by int2 is 0, then int2 is + the gcd */ + if ((temp = int1 % int2) == 0) { + return(int2); + } + /* Make int2 the larger integer and the remainder the + smaller integer, and repeat the process */ + int1 = int2; + int2 = temp; + } +} +``` #### Patient Optimization {#Heading7} diff --git a/10-04.md b/10-04.md index 4d13589..1ec43a5 100644 --- a/10-04.md +++ b/10-04.md @@ -12,93 +12,95 @@ pages: 200-203 **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 - ; 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 - ; repeated. Avoids code recursion. - ; - ; - ; - ; C near-callable as: - ; unsigned int gcd(unsigned int int1, unsigned int int2); +```nasm +; Finds and returns the greatest common divisor of two integers. +; 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 +; repeated. Avoids code recursion. +; +; +; +; C near-callable as: +; unsigned int gcd(unsigned int int1, unsigned int int2); - ; Parameter structure: - parms struc - dw ? ;pushed BP - dw ? ;pushed return address - int1 dw ? ;integers for which to find - int2 dw ? ; the GCD - parms ends +; Parameter structure: +parms struc + dw ? ;pushed BP + dw ? ;pushed return address +int1 dw ? ;integers for which to find +int2 dw ? ; the GCD +parms ends - .model small - .code - public _gcd - align 2 - _gcd proc near - push bp ;preserve caller's stack frame - mov bp,sp ;set up our stack frame - push si ;preserve caller's register variables - push di + .model small + .code + public _gcd + align 2 +_gcd proc near + push bp ;preserve caller's stack frame + mov bp,sp ;set up our stack frame + 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 - xchg ax,bx ;no, so swap int1 and int2 - IntsSet: +;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 + 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 - ; int2 to int1, and the remainder to int2, then repeat. - GCDLoop: - ;if the remainder of int1 divided by - ; int2 is 0, then int2 is the gcd - sub dx,dx ;prepare int1 in DX:AX for division - div bx ;int1/int2; remainder is in DX - and dx,dx ;is the remainder zero? - jz Done ;yes, so int2 (BX) is the gcd - ;no, so move int2 to int1 and the - ; remainder to int2, and repeat the - ; process - mov ax,bx ;int1 = int2; - mov bx,dx ;int2 = remainder from DIV +; 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 +; int2 to int1, and the remainder to int2, then repeat. +GCDLoop: + ;if the remainder of int1 divided by + ; int2 is 0, then int2 is the gcd + sub dx,dx ;prepare int1 in DX:AX for division + div bx ;int1/int2; remainder is in DX + and dx,dx ;is the remainder zero? + jz Done ;yes, so int2 (BX) is the gcd + ;no, so move int2 to int1 and the + ; remainder to int2, and repeat the + ; process + mov ax,bx ;int1 = int2; + mov bx,dx ;int2 = remainder from DIV - ;—start of loop unrolling; the above is repeated three times— - sub dx,dx ;prepare int1 in DX:AX for division - div bx ;int1/int2; remainder is in DX - and dx,dx ;is the remainder zero? - jz Done ;yes, so int2 (BX) is the gcd - mov ax,bx ;int1 = int2; - mov bx,dx ;int2 = remainder from DIV - ;— - sub dx,dx ;prepare int1 in DX:AX for division - div bx ;int1/int2; remainder is in DX - and dx,dx ;is the remainder zero? - jz Done ;yes, so int2 (BX) is the gcd - mov ax,bx ;int1 = int2; - mov bx,dx ;int2 = remainder from DIV - ;— - sub dx,dx ;prepare int1 in DX:AX for division - div bx ;int1/int2; remainder is in DX - and dx,dx ;is the remainder zero? - jz Done ;yes, so int2 (BX) is the gcd - mov ax,bx ;int1 = int2; - mov bx,dx ;int2 = remainder from DIV - ;—end of loop unrolling— - jmp GCDLoop +;—start of loop unrolling; the above is repeated three times— + sub dx,dx ;prepare int1 in DX:AX for division + div bx ;int1/int2; remainder is in DX + and dx,dx ;is the remainder zero? + jz Done ;yes, so int2 (BX) is the gcd + mov ax,bx ;int1 = int2; + mov bx,dx ;int2 = remainder from DIV +;— + sub dx,dx ;prepare int1 in DX:AX for division + div bx ;int1/int2; remainder is in DX + and dx,dx ;is the remainder zero? + jz Done ;yes, so int2 (BX) is the gcd + mov ax,bx ;int1 = int2; + mov bx,dx ;int2 = remainder from DIV +;— + sub dx,dx ;prepare int1 in DX:AX for division + div bx ;int1/int2; remainder is in DX + and dx,dx ;is the remainder zero? + jz Done ;yes, so int2 (BX) is the gcd + mov ax,bx ;int1 = int2; + mov bx,dx ;int2 = remainder from DIV +;—end of loop unrolling— + jmp GCDLoop - align2 - Done: - mov ax,bx ;return the GCD - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - _gcd endp - end + align2 +Done: + mov ax,bx ;return the GCD + pop di ;restore caller's register variables + pop si + 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 diff --git a/11-01.md b/11-01.md index 76fb416..3e8d120 100644 --- a/11-01.md +++ b/11-01.md @@ -97,13 +97,17 @@ 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] - mov dx,es +```nasm +les ax,dword ptr [LongVar] +mov dx,es +``` which loads **LongVar** into DX:AX faster than this: - mov ax,word ptr [LongVar] - mov dx,word ptr [LongVar+2] +```nasm +mov ax,word ptr [LongVar] +mov dx,word ptr [LongVar+2] +``` Protected mode uses those altered segment registers to offer access to a great deal more memory than real mode: The 286 supports 16 megabytes of diff --git a/11-02.md b/11-02.md index a0f19a3..5b61ab1 100644 --- a/11-02.md +++ b/11-02.md @@ -126,25 +126,27 @@ shortly.) **LISTING 11.1 L11-1.ASM** - ; - ; *** Listing 11.1 *** - ; - ; Measures the performance of an immediate move to - ; memory, in order to demonstrate that the prefetch - ; queue cycle-eater is alive and well on the AT. - ; - jmp Skip - ; - even ;always make sure word-sized memory - ; variables are word-aligned! - WordVar dw 0 - ; - Skip: - call ZTimerOn - rept 1000 - mov [WordVar],0 - endm - call ZTimerOff +```nasm +; +; *** Listing 11.1 *** +; +; Measures the performance of an immediate move to +; memory, in order to demonstrate that the prefetch +; queue cycle-eater is alive and well on the AT. +; + jmp Skip +; + even ;always make sure word-sized memory + ; variables are word-aligned! +WordVar dw 0 +; +Skip: + call ZTimerOn + rept 1000 + mov [WordVar],0 + endm + 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 diff --git a/11-03.md b/11-03.md index 551ebe8..fb589bd 100644 --- a/11-03.md +++ b/11-03.md @@ -101,23 +101,25 @@ refresh. **LISTING 11.2 L11-2.ASM** - ; - ; *** Listing 11.2 *** - ; - ; Measures the performance of accesses to word-sized - ; variables that start at odd addresses (are not - ; word-aligned). - ; - Skip: - push ds - pop es - mov si,1 ;source and destination are the same - mov di,si ; and both are not word-aligned - mov cx,1000 ;move 1000 words - cld - call ZTimerOn - rep movsw - call ZTimerOff +```nasm +; +; *** Listing 11.2 *** +; +; Measures the performance of accesses to word-sized +; variables that start at odd addresses (are not +; word-aligned). +; +Skip: + push ds + pop es + mov si,1 ;source and destination are the same + mov di,si ; and both are not word-aligned + mov cx,1000 ;move 1000 words + cld + call ZTimerOn + rep movsw + call ZTimerOff +``` 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 @@ -128,22 +130,24 @@ we predicted. **LISTING 11.3 L11-3.ASM** - ; - ; *** Listing 11.3 *** - ; - ; Measures the performance of accesses to word-sized - ; variables that start at even addresses (are word-aligned). - ; - Skip: - push ds - pop es - sub si,si ;source and destination are the same - mov di,si ; and both are word-aligned - mov cx,1000 ;move 1000 words - cld - call ZTimerOn - rep movsw - call ZTimerOff +```nasm +; +; *** Listing 11.3 *** +; +; Measures the performance of accesses to word-sized +; variables that start at even addresses (are word-aligned). +; +Skip: + push ds + pop es + sub si,si ;source and destination are the same + mov di,si ; and both are word-aligned + mov cx,1000 ;move 1000 words + cld + call ZTimerOn + rep movsw + call ZTimerOff +``` 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 diff --git a/11-04.md b/11-04.md index 56d61c3..124146d 100644 --- a/11-04.md +++ b/11-04.md @@ -15,12 +15,13 @@ AT clone to verify the basic functionality of the timer by measuring the performance of simple instruction sequences. I was cruising along with no problems until I timed the following code: - - mov cx,1000 - call ZTimerOn - LoopTop: - loop LoopTop - call ZTimerOff +```nasm + mov cx,1000 + call ZTimerOn +LoopTop: + loop LoopTop + call ZTimerOff +``` ![**Figure 11.2**  *Word-aligned prefetching on the 286.*](images/11-02.jpg) @@ -39,12 +40,14 @@ resided at the start of the next word-aligned word. One simple change brought the execution time down to a reasonable 12.5 cycles per loop: - mov cx,1000 - call ZTimerOn - even - LoopTop: - loop LoopTop - call ZTimerOff +```nasm + mov cx,1000 + call ZTimerOn + even +LoopTop: + loop LoopTop + call ZTimerOff +``` While word-aligning branch destinations can improve branching performance, it's a nuisance and can increase code size a good deal, so @@ -60,9 +63,11 @@ branch destination. I recommend that you only go out of your way to word-align the start offsets of your subroutines, as in: - even - FindChar proc near - : +```nasm + even +FindChar proc near + : +``` In my experience, this simple practice is the one form of code alignment that consistently provides a reasonable return for bytes and effort diff --git a/11-06.md b/11-06.md index dc765fb..d950a03 100644 --- a/11-06.md +++ b/11-06.md @@ -113,15 +113,17 @@ Theory confirmed. **LISTING 11.4 L11-4.ASM** - ; - ; *** Listing 11.4 *** - ; - ; Measures the performance of adding an immediate value - ; to a register, for comparison with Listing 11.5, which - ; adds an immediate value to a memory variable. - ; - call ZTimerOn - rept 1000 - add dx,100h - endm - call ZTimerOff +```nasm +; +; *** Listing 11.4 *** +; +; Measures the performance of adding an immediate value +; to a register, for comparison with Listing 11.5, which +; adds an immediate value to a memory variable. +; + call ZTimerOn + rept 1000 + add dx,100h + endm + call ZTimerOff +``` diff --git a/11-07.md b/11-07.md index 4c0f993..c06499b 100644 --- a/11-07.md +++ b/11-07.md @@ -12,25 +12,27 @@ pages: 224-226 **LISTING 11.5 L11-5.ASM** - ; - ; *** Listing 11.5 *** - ; - ; Measures the performance of adding an immediate value - ; to a memory variable, for comparison with Listing 11.4, - ; which adds an immediate value to a register. - ; - jmp Skip - ; - even ;always make sure word-sized memory - ; variables are word-aligned! - WordVar dw 0 - ; - Skip: - call ZTimerOn - rept 1000 - add [WordVar]100h - endm - call ZTimerOff +```nasm +; +; *** Listing 11.5 *** +; +; Measures the performance of adding an immediate value +; to a memory variable, for comparison with Listing 11.4, +; which adds an immediate value to a register. +; + jmp Skip +; + even ;always make sure word-sized memory + ; variables are word-aligned! +WordVar dw 0 +; +Skip: + call ZTimerOn + rept 1000 + add [WordVar]100h + endm + call ZTimerOff +``` What's going on? Simply this: Instruction fetching is controlling overall execution time on *both* processors. Both the 8088 in a PC and diff --git a/11-08.md b/11-08.md index c3edbae..7592c66 100644 --- a/11-08.md +++ b/11-08.md @@ -43,22 +43,24 @@ 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: - jmpshort popfskip - popfiret: - iret; branches to the instruction after the - ; call, popping the word below the address - ; pushed by CALL into the FLAGS register - popfskip: - call far ptr popfiret - ;pushes the segment:offset of the next - ; instruction on the stack just above - ; the flags word, setting things up so - ; that IRET will branch to the next - ; instruction and pop the flags - ; When execution reaches the instruction following this comment, - ; the word that was on top of the stack when JMP SHORT POPFSKIP - ; was reached has been popped into the FLAGS register, just as - ; if a POPF instruction had been executed. +```nasm + jmpshort popfskip +popfiret: + iret; branches to the instruction after the + ; call, popping the word below the address + ; pushed by CALL into the FLAGS register +popfskip: + call far ptr popfiret + ;pushes the segment:offset of the next + ; instruction on the stack just above + ; the flags word, setting things up so + ; that IRET will branch to the next + ; instruction and pop the flags +; When execution reaches the instruction following this comment, +; the word that was on top of the stack when JMP SHORT POPFSKIP +; was reached has been popped into the FLAGS register, just as +; if a POPF instruction had been executed. +``` ![**Figure 11.5**  *The operation of IRET.*](images/11-05.jpg) @@ -68,26 +70,30 @@ The **POPF** workaround can best be implemented as a macro; we can also emulate a far call by pushing CS and performing a near call, thereby shrinking the workaround code by 1 byte: - EMULATE_POPF macro - local popfskip, popfiret - jmp short popfskip - popfiret: - iret - popfskip: - push cs - call popfiret - endm +```nasm +EMULATE_POPF macro + local popfskip, popfiret + jmp short popfskip +popfiret: + iret +popfskip: + push cs + call popfiret + endm +``` 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: - EMULATE_POPF_TRASH_AX macro - push cs - mov ax,offset $+5 - push ax - iret - endm +```nasm +EMULATE_POPF_TRASH_AX macro + push cs + mov ax,offset $+5 + push 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 @@ -96,13 +102,15 @@ 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 an 8088.) - .286 - : - EMULATE_POPFmacro - pushcs - pushoffset $+4 - iret - endm +```nasm + .286 + : +EMULATE_POPFmacro + pushcs + pushoffset $+4 + iret + endm +``` ![**Figure 11.6**  *Workaround code for the POPF bug.*](images/11-06.jpg) diff --git a/12-01.md b/12-01.md index 40d4a93..f179f94 100644 --- a/12-01.md +++ b/12-01.md @@ -128,18 +128,22 @@ matter what register is used.) **MOV AX, [BX+DI]** and **MOV CL, As an example, you might adhere to this rule by replacing the code - LoopTop: - add ax,[bx+si] - add si,2 - dec cx - jnz LoopTop +```nasm +LoopTop: + add ax,[bx+si] + add si,2 + dec cx + jnz LoopTop +``` with this - add si,bx - LoopTop: - add ax,[si] - add si,2 - dec cx - jnz LoopTop - sub si,bx +```nasm + add si,bx +LoopTop: + add ax,[si] + add si,2 + dec cx + jnz LoopTop + sub si,bx +``` diff --git a/12-02.md b/12-02.md index fcc55ce..5dc7ef6 100644 --- a/12-02.md +++ b/12-02.md @@ -58,8 +58,10 @@ 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] +```nasm +MOV BX,OFFSET MemVar +MOV AX,[BX] +``` there's no way that the 486 can calculate the address referenced by **MOV AX,[BX]** until **MOV BX,OFFSET MemVar** finishes, so pipelining @@ -68,19 +70,23 @@ rearranging your code so that at least one instruction lies between the loading of the memory pointer and its use. For example, postdecrementing, as in the following - LoopTop: - add ax,[si] - add si,2 - dec cx - jnz LoopTop +```nasm +LoopTop: + add ax,[si] + add si,2 + dec cx + jnz LoopTop +``` is faster than preincrementing, as in: - LoopTop: - add si,2 - add ax,[SI] - dec cx - jnz LoopTop +```nasm +LoopTop: + add si,2 + add ax,[SI] + dec cx + jnz LoopTop +``` 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 @@ -108,28 +114,34 @@ instructions, but, because some 486 instructions take more than 1 cycle, 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 - mov ax,[bx] - inc dx - dec cx - jnz LoopTop +```nasm +mov bx,offset MemVar +mov ax,[bx] +inc dx +dec cx +jnz LoopTop +``` loses two cycles from interrupting the address calculation pipeline, this code - mov bx,offset MemVar - inc dx - mov ax,[bx] - dec cx - jnz LoopTop +```nasm +mov bx,offset MemVar +inc dx +mov ax,[bx] +dec cx +jnz LoopTop +``` loses only one cycle, and this code - mov bx,offset MemVar - inc dx - dec cx - mov ax,[bx] - jnz LoopTop +```nasm +mov bx,offset MemVar +inc dx +dec cx +mov ax,[bx] +jnz LoopTop +``` loses no cycles at all. Apparently, the 486's addressing calculation pipeline actually starts 2 cycles ahead, as shown in Figure 12.2. (In diff --git a/12-03.md b/12-03.md index bbc991a..ee26fd1 100644 --- a/12-03.md +++ b/12-03.md @@ -41,22 +41,26 @@ pops up only spottily when the stack pointer is involved. For example, you'd certainly expect a sequence such as - : - pop ax - ret - pop ax - et - : +```nasm +: +pop ax +ret +pop ax +et +: +``` to exhibit the addressing pipeline interruption phenomenon (SP is both destination and addressing register for both instructions, according to Intel), but this code runs in six cycles per **POP/RET** pair, matching the official execution times exactly. Likewise, a sequence like - pop dx - pop cx - pop bx - pop ax +```nasm +pop dx +pop cx +pop bx +pop ax +``` runs in one cycle per instruction, just as it should. @@ -65,15 +69,19 @@ destination—for example, to deallocate local variables—and then using **PUSH**, **POP**, or **RET**, definitely can interrupt the addressing pipeline. For example - add sp,10h - ret +```nasm +add sp,10h +ret +``` loses two cycles because SP is the explicit destination of one instruction and then the implied addressing register for the next, and the sequence - add sp,10h - pop ax +```nasm +add sp,10h +pop ax +``` loses two cycles for the same reason. @@ -101,22 +109,26 @@ during the next instruction. So, for example, it would be a bad idea to do this - mov ah,o - : - mov cx,[MemVar1] - mov al,[MemVar2] - add cx,ax +```nasm +mov ah,o + : +mov cx,[MemVar1] +mov al,[MemVar2] +add cx,ax +``` 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 immediately followed by the word register usage, like so: - mov ah,o - : - mov al,[MemVar2] - mov cx,[MemVar1] - add cx,ax +```nasm +mov ah,o + : +mov al,[MemVar2] +mov cx,[MemVar1] +add cx,ax +``` Strange as it may seem, this rule is neither arbitrary nor nonsensical. Basically, when a byte destination register is part of a word source @@ -135,10 +147,12 @@ works. In case you're curious, there's no such penalty for the typical **XLAT** sequence like - mov bx,offset MemTable - : - mov al,[si] - xlat +```nasm +mov bx,offset MemTable + : +mov al,[si] +xlat +``` even though AL must be converted to a word by **XLAT** before it can be added to BX and used to address memory. In fact, none of the penalties diff --git a/12-04.md b/12-04.md index 0b47ff0..a542d9f 100644 --- a/12-04.md +++ b/12-04.md @@ -24,9 +24,11 @@ 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 - mov al,bl - mov cx,dx - mov si,[di] +```nasm +mov al,bl +mov cx,dx +mov si,[di] +``` takes four rather than the expected three cycles to execute. Note that it is *not* required that the byte register be part of the register used @@ -35,17 +37,20 @@ to address memory; any byte register will do the trick. Worse still, loading byte registers both one and two cycles before a register is used to address memory costs two cycles, as in - mov bl,al - mov cl,3 - mov bx,[si] - +```nasm +mov bl,al +mov cl,3 +mov bx,[si] +``` which takes five rather than three cycles to run. However, there is *no* penalty if a byte register is loaded one cycle but not two cycles before a register is used to address memory. Therefore, - mov cx,3 - mov dl,al - mov si,[bx] +```nasm +mov cx,3 +mov dl,al +mov si,[bx] +``` runs in the expected three cycles. @@ -57,9 +62,11 @@ its interaction with the other rules—could lead to considerable performance loss in seemingly air-tight code. For instance, a casual observer would expect the following code to run in 3 cycles: - mov bx,offset MemVar - mov cl,al - mov ax,[bx] +```nasm +mov bx,offset MemVar +mov cl,al +mov ax,[bx] +``` A more sophisticated programmer would expect to lose one cycle, because BX is loaded two cycles before being used to address memory. In fact, @@ -97,43 +104,47 @@ addressing memory. **LISTING 12.1 LST12-1.ASM** - ; 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 - sub bx,bx - CacheFillLoop: - call ZTimerOn ;start timing - rept 1000 - mov dl,cl - nop - mov ax,[bx] - endm - call ZTimerOff ;stop timing - dec bp - jz Done - jmp CacheFillLoop - Done: +```nasm +; 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 + sub bx,bx +CacheFillLoop: + call ZTimerOn ;start timing + rept 1000 + mov dl,cl + nop + mov ax,[bx] + endm + call ZTimerOff ;stop timing + dec bp + jz Done + jmp CacheFillLoop +Done: +``` **LISTING 12.2 LST12-2.ASM** - ; 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 - sub bx,bx - CacheFillLoop: - call ZTimerOn ;start timing - rept 1000 - nop - mov dl,cl - mov ax,[bx] - endm - call ZTimerOff ;stop timing - dec bp - jz Done - jmp CacheFillLoop - Done: +```nasm +; 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 + sub bx,bx +CacheFillLoop: + call ZTimerOn ;start timing + rept 1000 + nop + mov dl,cl + mov ax,[bx] + endm + call ZTimerOff ;stop timing + dec bp + jz Done + jmp CacheFillLoop +Done: +``` 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 diff --git a/13-01.md b/13-01.md index b4bd0af..3593f33 100644 --- a/13-01.md +++ b/13-01.md @@ -78,10 +78,12 @@ also lines, at a rate of three instructions for every two characters! **LISTING 13.1 L13-1.ASM** - mov di,[bp+OFFS] ;get the next pair of characters - mov bl,[di] ;get the state value for the pair - add dx,[bx+8000h] ;increment word and line count - ; appropriately for the pair +```nasm +mov di,[bp+OFFS] ;get the next pair of characters +mov bl,[di] ;get the state value for the pair +add dx,[bx+8000h] ;increment word and line count + ; appropriately for the pair +``` Listing 13.1 looks as tight as it could be, with just two one-cycle instructions, one two-cycle instruction, and no branches. It *is* tight, @@ -113,10 +115,12 @@ the intervening instruction takes two cycles, there's no penalty at all. **LISTING 13.2 L13-2.ASM** - mov bl,[di] ;get the state value for the pair - mov di,[bp+OFFS] ;get the next pair of characters - add dx,[bx+8000h] ;increment word and line count - ; appropriately for the pair +```nasm +mov bl,[di] ;get the state value for the pair +mov di,[bp+OFFS] ;get the next pair of characters +add dx,[bx+8000h] ;increment word and line count + ; appropriately for the pair +``` At this point, Terje had nearly doubled the performance of this code simply by moving one instruction. (Note that swapping the instructions diff --git a/13-02.md b/13-02.md index d71c07d..b0489e5 100644 --- a/13-02.md +++ b/13-02.md @@ -22,18 +22,22 @@ throughput of two cycles/char." **LISTING 13.3 L13-3.ASM** - mov bl,[di] ;get the state value for the pair - mov di,[bp+OFFS] ;get the next pair of characters - mov ax,[bx+8000h] ;increment word and line count - add dx,ax ; appropriately for the pair +```nasm +mov bl,[di] ;get the state value for the pair +mov di,[bp+OFFS] ;get the next pair of characters +mov ax,[bx+8000h] ;increment word and line count +add dx,ax ; appropriately for the pair +``` **LISTING 13.4 L13-4.ASM** - mov bl,[di] ;get the state value for the pair - mov di,[bp+OFFS] ;get the next pair of characters - add dx,ax ;increment word and line count - ; appropriately for the pair - mov ax,[bx+8000h] ;get increments for next time +```nasm +mov bl,[di] ;get the state value for the pair +mov di,[bp+OFFS] ;get the next pair of characters +add dx,ax ;increment word and line count + ; 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 cycle that Terje saved in Listing 13.4 sped up his entire word-counting @@ -57,9 +61,11 @@ and 16-bit registers are not valid operands.) The obvious use of first in memory, also called *little endian*) to Motorola format (most significant byte first in memory, or *big endian*), like so: - lodsd - bswap - stosd +```nasm +lodsd +bswap +stosd +``` **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 @@ -94,17 +100,19 @@ memory, isn't it? **LISTING 13.5 L13-5.ASM** - mov cx,[initialskip] - shl ecx,16 ;put skip value in upper half of ECX - mov cx,100 ;put loop count in CX - looptop: - : - ror ecx,16 ;make skip value word accessible in CX - add bx,cx ;skip BX ahead - inc cx ;set next skip value - ror ecx,16 ;put loop count in CX - dec cx ;count down loop - jnz looptop +```nasm +mov cx,[initialskip] +shl ecx,16 ;put skip value in upper half of ECX +mov cx,100 ;put loop count in CX +looptop: + : + ror ecx,16 ;make skip value word accessible in CX + add bx,cx ;skip BX ahead + inc cx ;set next skip value + ror ecx,16 ;put loop count in CX + dec cx ;count down loop + jnz looptop +``` Not necessarily. Shifts and rotates are among the worst performing instructions of the 486, taking 2 to 3 cycles to execute. Thus, it takes diff --git a/13-03.md b/13-03.md index f91ede5..2c4d70b 100644 --- a/13-03.md +++ b/13-03.md @@ -21,17 +21,19 @@ registers into one 32-bit register much more useful. **LISTING 13.6 L13-6.ASM** - mov cx,[initialskip] - bswap ecx ;put skip value in upper half of ECX - mov cx,100 ;put loop count in CX - looptop: - : - bswap ecx ;make skip value word accessible in CX - add bx,cx ;skip BX ahead - inc cx ;set next skip value - bswap ecx ;put loop count in CX - dec cx ;count down loop - jnz looptop +```nasm + mov cx,[initialskip] + bswap ecx ;put skip value in upper half of ECX + mov cx,100 ;put loop count in CX +looptop: + : + bswap ecx ;make skip value word accessible in CX + add bx,cx ;skip BX ahead + inc cx ;set next skip value + bswap ecx ;put loop count in CX + dec cx ;count down loop + jnz looptop +``` ### Pushing and Popping Memory {#Heading5} @@ -44,12 +46,16 @@ contrast, loading a memory location into a register takes only one cycle, and pushing a register takes just 1 more cycle, for a total of two cycles. Therefore, - mov ax,[bx] - push ax +```nasm +mov ax,[bx] +push ax +``` is twice as fast as - push word ptr [bx] +```nasm +push word ptr [bx] +``` and the only cost is that the previous contents of AX are destroyed. @@ -98,9 +104,11 @@ disassembly in a debugger or by having the assembler generate a listing file. You could then insert the n-bit version of **SHL AX,1** in your code as follows: - mov ax,1 - db 0c1h, 0e0h, 001h - mov dx,ax +```nasm +mov ax,1 +db 0c1h, 0e0h, 001h +mov dx,ax +``` At the end of this sequence, DX will contain 2, and the fast n-bit version of **SHL AX,1** will have executed. If you use this approach, diff --git a/13-04.md b/13-04.md index 67ae17d..1cd37ca 100644 --- a/13-04.md +++ b/13-04.md @@ -17,7 +17,9 @@ register may serve as the base memory addressing register, and almost any register may serve as the potentially scaled index register. For example, - mov al,BaseTable[ecx+edx*4] +```nasm +mov al,BaseTable[ecx+edx*4] +``` uses a perfectly valid 32-bit address, with the byte accessed being the one at the offset in DS pointed to by the sum of EDX times 4 plus the @@ -82,21 +84,25 @@ 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: - add ax,DataTable[ebx*2] - inc ebx - dec cx - jnz LoopTop +```nasm +LoopTop: + add ax,DataTable[ebx*2] + inc ebx + dec cx + jnz LoopTop +``` You could change this to the following for greater performance: - add ebx,ebx ;ebx*2 - LoopTop: - add ax,DataTable[ebx] - add ebxX,2 - dec cx - jnz LoopTop - shr ebx,1 ;ebx*2/2 +```nasm + add ebx,ebx ;ebx*2 +LoopTop: + add ax,DataTable[ebx] + add ebxX,2 + dec cx + jnz LoopTop + shr ebx,1 ;ebx*2/2 +``` 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 diff --git a/14-04.md b/14-04.md index 93a54fa..32fccfa 100644 --- a/14-04.md +++ b/14-04.md @@ -12,148 +12,152 @@ pages: 268-271 **LISTING 14.1 L14-1.C** - /* Searches a buffer for a specified pattern. In case of a mismatch, - uses the value of the mismatched byte to skip across as many - potential match locations as possible (partial Boyer-Moore). - Returns start offset of first match searching forward, or NULL if - no match is found. - Tested with Borland C++ in C mode and the small model. */ +```c +/* Searches a buffer for a specified pattern. In case of a mismatch, + uses the value of the mismatched byte to skip across as many + potential match locations as possible (partial Boyer-Moore). + Returns start offset of first match searching forward, or NULL if + no match is found. + Tested with Borland C++ in C mode and the small model. */ - #include +#include - unsigned char * FindString(unsigned char * BufferPtr, - unsigned int BufferLength, unsigned char * PatternPtr, - unsigned int PatternLength) - { - unsigned char * WorkingPatternPtr, * WorkingBufferPtr; - unsigned int CompCount, SkipTable[256], Skip, DistanceMatched; - int i; +unsigned char * FindString(unsigned char * BufferPtr, + unsigned int BufferLength, unsigned char * PatternPtr, + unsigned int PatternLength) +{ + unsigned char * WorkingPatternPtr, * WorkingBufferPtr; + unsigned int CompCount, SkipTable[256], Skip, DistanceMatched; + int i; - /* Reject if the buffer is too small */ - if (BufferLength < PatternLength) return(NULL); + /* Reject if the buffer is too small */ + if (BufferLength < PatternLength) return(NULL); - /* Return an instant match if the pattern is 0-length */ - if (PatternLength == 0) return(BufferPtr); + /* Return an instant match if the pattern is 0-length */ + if (PatternLength == 0) return(BufferPtr); - /* 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 */ - 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; - 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 */ - for (i = 0; i < (PatternLength - 1); i++) - SkipTable[PatternPtr[i]] = PatternLength - i - 1; + /* 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 */ + 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; + 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 */ + for (i = 0; i < (PatternLength - 1); i++) + SkipTable[PatternPtr[i]] = PatternLength - i - 1; - /* Point to rightmost byte of the pattern */ - PatternPtr += PatternLength - 1; - /* Point to last (rightmost) byte of the first potential pattern - match location in the buffer */ - BufferPtr += PatternLength - 1; - /* Count of number of potential pattern match locations in - buffer */ - BufferLength -= PatternLength - 1; + /* Point to rightmost byte of the pattern */ + PatternPtr += PatternLength - 1; + /* Point to last (rightmost) byte of the first potential pattern + match location in the buffer */ + BufferPtr += PatternLength - 1; + /* Count of number of potential pattern match locations in + buffer */ + BufferLength -= PatternLength - 1; - /* Search the buffer */ - while (1) { - /* See if we have a match at this buffer location */ - WorkingPatternPtr = PatternPtr; - WorkingBufferPtr = BufferPtr; - CompCount = PatternLength; - /* 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 (-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 */ - 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 - 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 */ - 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 - without a match */ - if (Skip >= BufferLength) return(NULL); - /* Skip ahead and perform the next comparison */ - BufferLength -= Skip; - BufferPtr += Skip; - } - } + /* Search the buffer */ + while (1) { + /* See if we have a match at this buffer location */ + WorkingPatternPtr = PatternPtr; + WorkingBufferPtr = BufferPtr; + CompCount = PatternLength; + /* 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 (-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 */ + 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 + 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 */ + 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 + without a match */ + if (Skip >= BufferLength) return(NULL); + /* Skip ahead and perform the next comparison */ + BufferLength -= Skip; + BufferPtr += Skip; + } +} +``` **LISTING 14.2 L14-2.C** - /* Program to exercise buffer-search routines in Listings 14.1 & 14.3. - (Must be modified to put copy of pattern as sentinel at end of the - search buffer in order to be used with Listing 14.4.) */ +```c +/* Program to exercise buffer-search routines in Listings 14.1 & 14.3. + (Must be modified to put copy of pattern as sentinel at end of the + search buffer in order to be used with Listing 14.4.) */ - #include - #include - #include +#include +#include +#include - #define DISPLAY_LENGTH 40 - #define BUFFER_SIZE 0x8000 +#define DISPLAY_LENGTH 40 +#define BUFFER_SIZE 0x8000 - extern unsigned char * FindString(unsigned char *, unsigned int, - unsigned char *, unsigned int); - void main(void); +extern unsigned char * FindString(unsigned char *, unsigned int, + unsigned char *, unsigned int); +void main(void); - void main() { - unsigned char TempBuffer[DISPLAY_LENGTH+1]; - unsigned char Filename[150], Pattern[150], *MatchPtr, *TestBuffer; - int Handle; - unsigned int WorkingLength; +void main() { + unsigned char TempBuffer[DISPLAY_LENGTH+1]; + unsigned char Filename[150], Pattern[150], *MatchPtr, *TestBuffer; + int Handle; + unsigned int WorkingLength; - printf("File to search:"); - gets(Filename); - printf("Pattern for which to search:"); - gets(Pattern); + printf("File to search:"); + gets(Filename); + 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); - } - /* 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); - } - /* Process a BUFFER_SIZE chunk */ - if ( (int)(WorkingLength = - read(Handle, TestBuffer, BUFFER_SIZE)) == -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); - } else { - /* Pattern was found. Zero-terminate TempBuffer; strncpy - 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", - Pattern, DISPLAY_LENGTH, - strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH)); - } - exit(0); - } + if ( (Handle = open(Filename, O_RDONLY | O_BINARY)) == -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); + } + /* Process a BUFFER_SIZE chunk */ + if ( (int)(WorkingLength = + read(Handle, TestBuffer, BUFFER_SIZE)) == -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); + } else { + /* Pattern was found. Zero-terminate TempBuffer; strncpy + 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", + Pattern, DISPLAY_LENGTH, + strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH)); + } + exit(0); +} +``` 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 diff --git a/14-05.md b/14-05.md index c07bc4f..478c94d 100644 --- a/14-05.md +++ b/14-05.md @@ -12,163 +12,165 @@ pages: 271-274 **LISTING 14.3 L14-3.ASM** - ; Searches a buffer for a specified pattern. In case of a mismatch, - ; uses the value of the mismatched byte to skip across as many - ; potential match locations as possible (partial Boyer-Moore). - ; Returns start offset of first match searching forward, or NULL if - ; no match is found. - ; Tested with TASM. - ; C near-callable as: - ; unsigned char * FindString(unsigned char * BufferPtr, - ; unsigned int BufferLength, unsigned char * PatternPtr, - ; unsigned int PatternLength); +```nasm +; Searches a buffer for a specified pattern. In case of a mismatch, +; uses the value of the mismatched byte to skip across as many +; potential match locations as possible (partial Boyer-Moore). +; Returns start offset of first match searching forward, or NULL if +; no match is found. +; Tested with TASM. +; C near-callable as: +; unsigned char * FindString(unsigned char * BufferPtr, +; unsigned int BufferLength, unsigned char * PatternPtr, +; unsigned int PatternLength); - parms struc - dw 2 dup(?) ;pushed BP & return address - BufferPtr dw ? ;pointer to buffer to be searched - BufferLength dw ? ;# of bytes in buffer to be searched - PatternPtr dw ? ;pointer to pattern for which to search - PatternLength dw ? ;length of pattern for which to search - parms ends +parms struc + dw 2 dup(?) ;pushed BP & return address +BufferPtr dw ? ;pointer to buffer to be searched +BufferLength dw ? ;# of bytes in buffer to be searched +PatternPtr dw ? ;pointer to pattern for which to search +PatternLength dw ? ;length of pattern for which to search +parms ends - .model small - .code - public _FindString - _FindString proc near - cld - push bp ;preserve caller's stack frame - mov bp,sp ;point to our stack frame - 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 - ; appear in the pattern. - mov ax,[bp+PatternLength] - and ax,ax ;return an instant match if the pattern is - jz InstantMatch ;0-length - mov di,ds - mov es,di ;ES=DS=SS - mov di,sp ;point to SkipBuffer - mov cx,256 - rep stosw - dec ax ;from now on, we only need - mov [bp+PatternLength],ax ; PatternLength - 1 - ; Point to last (rightmost) byte of first potential pattern match - ; location in buffer. - add [bp+BufferPtr],ax - ; Reject if buffer is too small, and set the count of the number of - ; potential pattern match locations in the buffer. - sub [bp+BufferLength],ax - jbe NoMatch - ; 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 - ; 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. - mov si,[bp+PatternPtr] ;point to start of pattern - and ax,ax ;are there any skips to set? - jz SetSkipDone ;no - mov di,sp ;point to SkipBuffer - SetSkipLoop: - sub bx,bx ;prepare for word addressing off byte value - mov bl,[si] ;get the next pattern byte - inc si ;advance the pattern pointer - shl bx,1 ;prepare for word lookup - mov [di+bx],ax ;set the skip value when this byte value is - ; the mismatch value in the buffer - dec ax - jnz SetSkipLoop - SetSkipDone: - mov dl,[si] ;DL=rightmost pattern byte from now on - dec si ;point to next-to-rightmost byte of pattern - mov [bp+PatternPtr],si ; from now on - ; Search the buffer. - std ;for backward REPZ CMPSB - mov di,[bp+BufferPtr] ;point to first search location - 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. - QuickSearchLoop: - mov bl,[di] ;rightmost buffer byte at this location - cmp dl,bl ;does it match the rightmost pattern byte? - jz FullCompare ;yes, so keep going - sub bh,bh ;convert to a word - add bx,bx ;prepare for look-up in SkipTable - mov ax,[si+bx] ;get skip value from skip table for this - ; mismatch value - add di,ax ;BufferPtr += Skip; - sub cx,ax ;BufferLength -= Skip; - ja QuickSearchLoop ;continue if any buffer left - jmp short NoMatch - ; Return a pointer to the start of the buffer (for 0-length pattern). - align 2 - InstantMatch: - mov ax,[bp+BufferPtr] - jmp short Done - ; Compare the pattern and the buffer location, searching from high - ; memory toward low (right to left). - align 2 - FullCompare: - mov [bp+BufferPtr],di ;save the current state of - mov [bp+BufferLength],cx ; the search - mov cx,[bp+PatternLength] ;# of bytes yet to compare - jcxz Match ;done if only one character - 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. - 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 - ; 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. - sub bx,bx ;prepare for word addressing off byte value - mov bl,[di] ;get the value of the mismatch byte in buffer - 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 - ; 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 - ; left to check. - mov di,[bp+BufferPtr] - add di,ax ;BufferPtr += Skip; - mov cx,[bp+BufferLength] - sub cx,ax ;BufferLength -= Skip; - ja SearchLoop ;continue if any buffer left - ; Return a NULL pointer for no match. - align 2 - NoMatch: - sub ax,ax - jmp short Done - ; Return start of match in buffer (BufferPtr - (PatternLength - 1)). - align 2 - Match: - mov ax,[bp+BufferPtr] - sub ax,[bp+PatternLength] - Done: - cld ;restore default direction flag - add sp,256*2 ;deallocate space for SkipTable - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - _FindString endp - end + .model small + .code + public _FindString +_FindString proc near + cld + push bp ;preserve caller's stack frame + mov bp,sp ;point to our stack frame + 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 +; appear in the pattern. + mov ax,[bp+PatternLength] + and ax,ax ;return an instant match if the pattern is + jz InstantMatch ;0-length + mov di,ds + mov es,di ;ES=DS=SS + mov di,sp ;point to SkipBuffer + mov cx,256 + rep stosw + dec ax ;from now on, we only need + mov [bp+PatternLength],ax ; PatternLength - 1 +; Point to last (rightmost) byte of first potential pattern match +; location in buffer. + add [bp+BufferPtr],ax +; Reject if buffer is too small, and set the count of the number of +; potential pattern match locations in the buffer. + sub [bp+BufferLength],ax + jbe NoMatch +; 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 +; 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. + mov si,[bp+PatternPtr] ;point to start of pattern + and ax,ax ;are there any skips to set? + jz SetSkipDone ;no + mov di,sp ;point to SkipBuffer +SetSkipLoop: + sub bx,bx ;prepare for word addressing off byte value + mov bl,[si] ;get the next pattern byte + inc si ;advance the pattern pointer + shl bx,1 ;prepare for word lookup + mov [di+bx],ax ;set the skip value when this byte value is + ; the mismatch value in the buffer + dec ax + jnz SetSkipLoop +SetSkipDone: + mov dl,[si] ;DL=rightmost pattern byte from now on + dec si ;point to next-to-rightmost byte of pattern + mov [bp+PatternPtr],si ; from now on +; Search the buffer. + std ;for backward REPZ CMPSB + mov di,[bp+BufferPtr] ;point to first search location + 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. +QuickSearchLoop: + mov bl,[di] ;rightmost buffer byte at this location + cmp dl,bl ;does it match the rightmost pattern byte? + jz FullCompare ;yes, so keep going + sub bh,bh ;convert to a word + add bx,bx ;prepare for look-up in SkipTable + mov ax,[si+bx] ;get skip value from skip table for this + ; mismatch value + add di,ax ;BufferPtr += Skip; + sub cx,ax ;BufferLength -= Skip; + ja QuickSearchLoop ;continue if any buffer left + jmp short NoMatch +; Return a pointer to the start of the buffer (for 0-length pattern). + align 2 +InstantMatch: + mov ax,[bp+BufferPtr] + jmp short Done +; Compare the pattern and the buffer location, searching from high +; memory toward low (right to left). + align 2 +FullCompare: + mov [bp+BufferPtr],di ;save the current state of + mov [bp+BufferLength],cx ; the search + mov cx,[bp+PatternLength] ;# of bytes yet to compare + jcxz Match ;done if only one character + 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. + 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 +; 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. + sub bx,bx ;prepare for word addressing off byte value + mov bl,[di] ;get the value of the mismatch byte in buffer + 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 + ; 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 +; left to check. + mov di,[bp+BufferPtr] + add di,ax ;BufferPtr += Skip; + mov cx,[bp+BufferLength] + sub cx,ax ;BufferLength -= Skip; + ja SearchLoop ;continue if any buffer left +; Return a NULL pointer for no match. + align 2 +NoMatch: + sub ax,ax + jmp short Done +; Return start of match in buffer (BufferPtr - (PatternLength - 1)). + align 2 +Match: + mov ax,[bp+BufferPtr] + sub ax,[bp+PatternLength] +Done: + cld ;restore default direction flag + add sp,256*2 ;deallocate space for SkipTable + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame + ret +_FindString endp + end +``` diff --git a/14-06.md b/14-06.md index 0cc5491..8d5508d 100644 --- a/14-06.md +++ b/14-06.md @@ -35,152 +35,154 @@ about 60 percent faster than Listing 14.3. **LISTING 14.4 L14-4.ASM** - ; Searches a buffer for a specified pattern. In case of a mismatch, - ; uses the value of the mismatched byte to skip across as many - ; potential match locations as possible (partial Boyer-Moore). - ; Returns start offset of first match searching forward, or NULL if - ; no match is found. - ; 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). - ; Tested with TASM. - ; C near-callable as: - ; unsigned char * FindString(unsigned char * BufferPtr, - ; unsigned int BufferLength, unsigned char * PatternPtr, - ; unsigned int PatternLength); +```nasm +; Searches a buffer for a specified pattern. In case of a mismatch, +; uses the value of the mismatched byte to skip across as many +; potential match locations as possible (partial Boyer-Moore). +; Returns start offset of first match searching forward, or NULL if +; no match is found. +; 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). +; Tested with TASM. +; C near-callable as: +; unsigned char * FindString(unsigned char * BufferPtr, +; unsigned int BufferLength, unsigned char * PatternPtr, +; unsigned int PatternLength); - parms struc - dw 2 dup(?) ;pushed BP & return address - BufferPtr dw ? ;pointer to buffer to be searched - BufferLength dw ? ;# of bytes in buffer to be searched - ; (not used, actually) - PatternPtr dw ? ;pointer to pattern for which to search - ; (pattern *MUST* exist in the buffer) - PatternLength dw ? ;length of pattern for which to search (must - ; be <= 255) - parms ends +parms struc + dw 2 dup(?) ;pushed BP & return address +BufferPtr dw ? ;pointer to buffer to be searched +BufferLength dw ? ;# of bytes in buffer to be searched + ; (not used, actually) +PatternPtr dw ? ;pointer to pattern for which to search + ; (pattern *MUST* exist in the buffer) +PatternLength dw ? ;length of pattern for which to search (must + ; be <= 255) +parms ends - .model small - .code - public _FindString - _FindString proc near - cld - push bp ;preserve caller's stack frame - mov bp,sp ;point to our stack frame - 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 - ; appear in the pattern. - mov di,ds - mov es,di ;ES=DS=SS - mov di,sp ;point to SkipBuffer - mov al,byte ptr [bp+PatternLength] - and al,al ;return an instant match if the pattern is - jz InstantMatch ; 0-length - mov ah,al - mov cx,256/2 - rep stosw - mov ax,[bp+PatternLength] - dec ax ;from now on, we only need - mov [bp+PatternLength],ax ; PatternLength - 1 - ; Point to rightmost byte of first potential pattern match location - ; in buffer. - add [bp+BufferPtr],ax - ; 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. - mov si,[bp+PatternPtr] ;point to start of pattern - and ax,ax ;are there any skips to set? - jz SetSkipDone ;no - mov di,sp ;point to SkipBuffer - sub bx,bx ;prepare for word addressing off byte value - SetSkipLoop: - mov bl,[si] ;get the next pattern byte - inc si ;advance the pattern pointer - mov [di+bx],al ;set the skip value when this byte value is - ;the mismatch value in the buffer - dec ax - jnz SetSkipLoop - SetSkipDone: - mov dl,[si] ;DL=rightmost pattern byte from now on - dec si ;point to next-to-rightmost byte of pattern - mov [bp+PatternPtr],si ; from now on - ; Search the buffer. - std ;for backward REPZ CMPSB - mov di,[bp+BufferPtr] ;point to the first search location - 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. - QuickSearchLoop: - ; See if we have a match at the first buffer location. - REPT 8 ;unroll loop 8 times to reduce branching - mov al,[di] ;next buffer byte - cmp dl,al ;does it match the pattern? - jz FullCompare ;yes, so keep going - xlat ;no, look up the skip value for this mismatch - add di,ax ;BufferPtr += Skip; - ENDM - jmp QuickSearchLoop - ; Return a pointer to the start of the buffer (for 0-length pattern). - align 2 - InstantMatch: - mov ax,[bp+BufferPtr] - jmp short Done - ; Compare the pattern and the buffer location, searching from high - ; memory toward low (right to left). - align 2 - FullCompare: - mov [bp+BufferPtr],di ;save the current buffer location - mov cx,[bp+PatternLength] ;# of bytes yet to compare - jcxz Match ;done if there was only one character - 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. - 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 - ; 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 - ; 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 - mov cx,ax ;yes, this is the distance to skip ahead from - ;the last potential match location checked - MoveAhead: - ; Skip ahead and perform the next comparison. - mov di,[bp+BufferPtr] - add di,cx ;BufferPtr += Skip; - mov si,[bp+PatternPtr] ;point to the next-to-rightmost - ; pattern byte - jmp SearchLoop - ; Return start of match in buffer (BufferPtr - (PatternLength - 1)). - align 2 - Match: - mov ax,[bp+BufferPtr] - sub ax,[bp+PatternLength] - Done: - cld ;restore default direction flag - add sp,256 ;deallocate space for SkipTable - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - _FindString endp - end + .model small + .code + public _FindString +_FindString proc near + cld + push bp ;preserve caller's stack frame + mov bp,sp ;point to our stack frame + 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 +; appear in the pattern. + mov di,ds + mov es,di ;ES=DS=SS + mov di,sp ;point to SkipBuffer + mov al,byte ptr [bp+PatternLength] + and al,al ;return an instant match if the pattern is + jz InstantMatch ; 0-length + mov ah,al + mov cx,256/2 + rep stosw + mov ax,[bp+PatternLength] + dec ax ;from now on, we only need + mov [bp+PatternLength],ax ; PatternLength - 1 +; Point to rightmost byte of first potential pattern match location +; in buffer. + add [bp+BufferPtr],ax +; 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. + mov si,[bp+PatternPtr] ;point to start of pattern + and ax,ax ;are there any skips to set? + jz SetSkipDone ;no + mov di,sp ;point to SkipBuffer + sub bx,bx ;prepare for word addressing off byte value +SetSkipLoop: + mov bl,[si] ;get the next pattern byte + inc si ;advance the pattern pointer + mov [di+bx],al ;set the skip value when this byte value is + ;the mismatch value in the buffer + dec ax + jnz SetSkipLoop +SetSkipDone: + mov dl,[si] ;DL=rightmost pattern byte from now on + dec si ;point to next-to-rightmost byte of pattern + mov [bp+PatternPtr],si ; from now on +; Search the buffer. + std ;for backward REPZ CMPSB + mov di,[bp+BufferPtr] ;point to the first search location + 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. +QuickSearchLoop: +; See if we have a match at the first buffer location. + REPT 8 ;unroll loop 8 times to reduce branching + mov al,[di] ;next buffer byte + cmp dl,al ;does it match the pattern? + jz FullCompare ;yes, so keep going + xlat ;no, look up the skip value for this mismatch + add di,ax ;BufferPtr += Skip; + ENDM + jmp QuickSearchLoop +; Return a pointer to the start of the buffer (for 0-length pattern). + align 2 +InstantMatch: + mov ax,[bp+BufferPtr] + jmp short Done +; Compare the pattern and the buffer location, searching from high +; memory toward low (right to left). + align 2 +FullCompare: + mov [bp+BufferPtr],di ;save the current buffer location + mov cx,[bp+PatternLength] ;# of bytes yet to compare + jcxz Match ;done if there was only one character + 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. + 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 +; 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 + ; 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 + mov cx,ax ;yes, this is the distance to skip ahead from + ;the last potential match location checked +MoveAhead: +; Skip ahead and perform the next comparison. + mov di,[bp+BufferPtr] + add di,cx ;BufferPtr += Skip; + mov si,[bp+PatternPtr] ;point to the next-to-rightmost + ; pattern byte + jmp SearchLoop +; Return start of match in buffer (BufferPtr - (PatternLength - 1)). + align 2 +Match: + mov ax,[bp+BufferPtr] + sub ax,[bp+PatternLength] +Done: + cld ;restore default direction flag + add sp,256 ;deallocate space for SkipTable + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame + ret +_FindString endp + end +``` Note that Table 14.1 includes the time required to build the skip table each time **FindString** is called. This time could be eliminated for diff --git a/15-02.md b/15-02.md index a64972a..47f1f61 100644 --- a/15-02.md +++ b/15-02.md @@ -12,57 +12,63 @@ pages: 284-287 **LISTING 15.1 L15-1.C** - /* Deletes the node in a linked list that follows the indicated node. - Assumes list is headed by a dummy node, so no special testing for - the head-of-list pointer is required. Returns the same pointer - that was passed in. */ +```c +/* Deletes the node in a linked list that follows the indicated node. + Assumes list is headed by a dummy node, so no special testing for + the head-of-list pointer is required. Returns the same pointer + that was passed in. */ - #include "llist.h" - struct LinkNode *DeleteNodeAfter(struct LinkNode *NodeToDeleteAfter) - { - NodeToDeleteAfter->NextNode = - NodeToDeleteAfter->NextNode->NextNode; - return(NodeToDeleteAfter); - } +#include "llist.h" +struct LinkNode *DeleteNodeAfter(struct LinkNode *NodeToDeleteAfter) +{ + NodeToDeleteAfter->NextNode = + NodeToDeleteAfter->NextNode->NextNode; + return(NodeToDeleteAfter); +} +``` **LISTING 15.2 LLIST.H** - /* Linked list header file. */ - #define MAX_TEXT_LENGTH 100 /* longest allowed Text field */ - #define SENTINEL 32767 /* largest possible Value field */ +```c +/* Linked list header file. */ +#define MAX_TEXT_LENGTH 100 /* longest allowed Text field */ +#define SENTINEL 32767 /* largest possible Value field */ - struct LinkNode { - struct LinkNode *NextNode; - int Value; - char Text[MAX_TEXT_LENGTH+1]; - /* Any number of additional data fields may by present */ - }; - struct LinkNode *DeleteNodeAfter(struct LinkNode *); - struct LinkNode *FindNodeBeforeValue(struct LinkNode *, int); - struct LinkNode *InitLinkedList(void); - struct LinkNode *InsertNodeSorted(struct LinkNode *, - struct LinkNode *); +struct LinkNode { + struct LinkNode *NextNode; + int Value; + char Text[MAX_TEXT_LENGTH+1]; + /* Any number of additional data fields may by present */ +}; +struct LinkNode *DeleteNodeAfter(struct LinkNode *); +struct LinkNode *FindNodeBeforeValue(struct LinkNode *, int); +struct LinkNode *InitLinkedList(void); +struct LinkNode *InsertNodeSorted(struct LinkNode *, + struct LinkNode *); +``` **LISTING 15.3 L15-3.C** - /* Deletes the node in the specified linked list that follows the - 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" - struct LinkNode *DeleteNodeAfter(struct LinkNode **HeadOfListPtr, - struct LinkNode *NodeToDeleteAfter) - { - /* Handle specially if the node to delete after is actually the - head of the list (delete the first element in the list) */ - if (NodeToDeleteAfter == (struct LinkNode *)HeadOfListPtr) { - *HeadOfListPtr = (*HeadOfListPtr)->NextNode; - } else { - NodeToDeleteAfter->NextNode = - NodeToDeleteAfter->NextNode->NextNode; - } - return(NodeToDeleteAfter); - } +```c +/* Deletes the node in the specified linked list that follows the + 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" +struct LinkNode *DeleteNodeAfter(struct LinkNode **HeadOfListPtr, + struct LinkNode *NodeToDeleteAfter) +{ + /* Handle specially if the node to delete after is actually the + head of the list (delete the first element in the list) */ + if (NodeToDeleteAfter == (struct LinkNode *)HeadOfListPtr) { + *HeadOfListPtr = (*HeadOfListPtr)->NextNode; + } else { + NodeToDeleteAfter->NextNode = + NodeToDeleteAfter->NextNode->NextNode; + } + return(NodeToDeleteAfter); +} +``` 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 @@ -123,28 +129,30 @@ value has to perform two tests in the inner loop, as shown in Listing **LISTING 15.4 L15-4.C** - /* Finds the first node in a linked list with a value field greater - than or equal to a key value, and returns a pointer to the node - preceding that node (to facilitate insertion and deletion), or a - 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" - struct LinkNode *FindNodeBeforeValueNotLess( - struct LinkNode *HeadOfListNode, int SearchValue) - { - struct LinkNode *NodePtr = HeadOfListNode; +```c +/* Finds the first node in a linked list with a value field greater + than or equal to a key value, and returns a pointer to the node + preceding that node (to facilitate insertion and deletion), or a + 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" +struct LinkNode *FindNodeBeforeValueNotLess( + struct LinkNode *HeadOfListNode, int SearchValue) +{ + struct LinkNode *NodePtr = HeadOfListNode; - while ( (NodePtr->NextNode->NextNode != NodePtr->NextNode) && - (NodePtr->NextNode->Value < SearchValue) ) - NodePtr = NodePtr->NextNode; + while ( (NodePtr->NextNode->NextNode != NodePtr->NextNode) && + (NodePtr->NextNode->Value < SearchValue) ) + NodePtr = NodePtr->NextNode; - if (NodePtr->NextNode->NextNode == NodePtr->NextNode) - return(NULL); /* we found the sentinel; failed search */ - else - return(NodePtr); /* success; return pointer to node preceding - node that was >= */ - } + if (NodePtr->NextNode->NextNode == NodePtr->NextNode) + return(NULL); /* we found the sentinel; failed search */ + else + return(NodePtr); /* success; return pointer to node preceding + node that was >= */ +} +``` 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 diff --git a/15-03.md b/15-03.md index f4c7215..96bd53b 100644 --- a/15-03.md +++ b/15-03.md @@ -12,27 +12,29 @@ pages: 287-290 **LISTING 15.5 L15-5.C** - /* Finds the first node in a value-sorted linked list that - has a Value field greater than or equal to a key value, and - returns a pointer to the node preceding that node (to facilitate - insertion and deletion), or a NULL pointer if no such value was - found. Assumes the list is terminated with a sentinel tail node - containing the largest possible Value field setting and pointing - to itself as the next node. */ - #include - #include "llist.h" - struct LinkNode *FindNodeBeforeValueNotLess( - struct LinkNode *HeadOfListNode, int SearchValue) - { - struct LinkNode *NodePtr = HeadOfListNode; - while (NodePtr->NextNode->Value < SearchValue) - NodePtr = NodePtr->NextNode; - if (NodePtr->NextNode->NextNode == NodePtr->NextNode) - return(NULL); /* we found the sentinel; failed search */ - else - return(NodePtr); /* success; return pointer to node preceding - node that was >= */ - } +```c +/* Finds the first node in a value-sorted linked list that + has a Value field greater than or equal to a key value, and + returns a pointer to the node preceding that node (to facilitate + insertion and deletion), or a NULL pointer if no such value was + found. Assumes the list is terminated with a sentinel tail node + containing the largest possible Value field setting and pointing + to itself as the next node. */ +#include +#include "llist.h" +struct LinkNode *FindNodeBeforeValueNotLess( + struct LinkNode *HeadOfListNode, int SearchValue) +{ + struct LinkNode *NodePtr = HeadOfListNode; + while (NodePtr->NextNode->Value < SearchValue) + NodePtr = NodePtr->NextNode; + if (NodePtr->NextNode->NextNode == NodePtr->NextNode) + return(NULL); /* we found the sentinel; failed search */ + else + return(NodePtr); /* success; return pointer to node preceding + node that was >= */ +} +``` ![**Figure 15.4**  *List terminated by a sentinel.*](images/15-04.jpg) @@ -80,68 +82,70 @@ before you write a single line of code. **LISTING 15.6 L15-6.C** - /* Suite of functions for maintaining a linked list sorted by - ascending order of the Value field. The list is circular; that - is,it has a dummy node as both the head and the tail of the list. - The dummy node is a sentinel, containing the largest possible - Value field setting. Tested with Borland C++ in C mode. */ - #include - #include - #include - #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. */ - struct LinkNode *InitLinkedList() - { - struct LinkNode *Sentinel; +```c +/* Suite of functions for maintaining a linked list sorted by + ascending order of the Value field. The list is circular; that + is,it has a dummy node as both the head and the tail of the list. + The dummy node is a sentinel, containing the largest possible + Value field setting. Tested with Borland C++ in C mode. */ +#include +#include +#include +#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. */ +struct LinkNode *InitLinkedList() +{ + struct LinkNode *Sentinel; - if ((Sentinel = malloc(sizeof(struct LinkNode))) == NULL) - return(NULL); - Sentinel->NextNode = Sentinel; - Sentinel->Value = SENTINEL; - strcpy(Sentinel->Text, "*** sentinel ***"); - return(Sentinel); - } + if ((Sentinel = malloc(sizeof(struct LinkNode))) == NULL) + return(NULL); + Sentinel->NextNode = Sentinel; + Sentinel->Value = SENTINEL; + strcpy(Sentinel->Text, "*** sentinel ***"); + return(Sentinel); +} - /* Finds the first node in a value-sorted linked list with a value - field equal to a key value, and returns a pointer to the node - preceding that node (to facilitate insertion and deletion), or a - NULL pointer if no value was found. Assumes list is terminated - with a sentinel node containing the largest possible value. */ +/* Finds the first node in a value-sorted linked list with a value + field equal to a key value, and returns a pointer to the node + preceding that node (to facilitate insertion and deletion), or a + NULL pointer if no value was found. Assumes list is terminated + with a sentinel node containing the largest possible value. */ - struct LinkNode *FindNodeBeforeValue(struct LinkNode *HeadOfListNode, - int SearchValue) - { - struct LinkNode *NodePtr = HeadOfListNode; +struct LinkNode *FindNodeBeforeValue(struct LinkNode *HeadOfListNode, +int SearchValue) +{ + struct LinkNode *NodePtr = HeadOfListNode; - while (NodePtr->NextNode->Value < SearchValue) - NodePtr = NodePtr->NextNode; - if (NodePtr->NextNode->Value == SearchValue) { - /* Found the search value; success unless we found the - sentinel (can happen only if SearchValue == SENTINEL) */ - if (NodePtr->NextNode == HeadOfListNode) { - return(NULL); /* failure; we found the sentinel */ - } else { - return(NodePtr); /* success; return pointer to node - preceding the node that was equal */ - } - } else { - return(NULL); /* No match; return failure status */ - } - } + while (NodePtr->NextNode->Value < SearchValue) + NodePtr = NodePtr->NextNode; + if (NodePtr->NextNode->Value == SearchValue) { + /* Found the search value; success unless we found the + sentinel (can happen only if SearchValue == SENTINEL) */ + if (NodePtr->NextNode == HeadOfListNode) { + return(NULL); /* failure; we found the sentinel */ + } else { + return(NodePtr); /* success; return pointer to node + preceding the node that was equal */ + } + } else { + return(NULL); /* No match; return failure status */ + } +} - /* Inserts the specified node into a value-sorted linked list, such - that value-sorting is maintained. Returns a pointer to the node - after which the new node is inserted. */ - struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode, - struct LinkNode *NodeToInsert) - { - struct LinkNode *NodePtr = HeadOfListNode; - int SearchValue = NodeToInsert->Value; - while (NodePtr->NextNode->Value < SearchValue) - NodePtr = NodePtr->NextNode; - NodeToInsert->NextNode = NodePtr->NextNode; - NodePtr->NextNode = NodeToInsert; - return(NodePtr); - } +/* Inserts the specified node into a value-sorted linked list, such + that value-sorting is maintained. Returns a pointer to the node + after which the new node is inserted. */ +struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode, + struct LinkNode *NodeToInsert) +{ + struct LinkNode *NodePtr = HeadOfListNode; + int SearchValue = NodeToInsert->Value; + while (NodePtr->NextNode->Value < SearchValue) + NodePtr = NodePtr->NextNode; + NodeToInsert->NextNode = NodePtr->NextNode; + NodePtr->NextNode = NodeToInsert; + return(NodePtr); +} +``` diff --git a/15-04.md b/15-04.md index 05b4164..29f68d3 100644 --- a/15-04.md +++ b/15-04.md @@ -12,149 +12,153 @@ pages: 290-293 **LISTING 15.7 L15-7.ASM** - ; C near-callable assembly function for inserting a new node in a - ; linked list sorted by ascending order of the Value field. The list - ; is circular; that is, it has a dummy node as both the head and the - ; tail of the list. The dummy node is a sentinel, containing the - ; largest possible Value field setting. Tested with TASM. - MAX_TEXT_LENGTH equ 100 ;longest allowed Text field - SENTINEL equ 32767 ;largest possible Value field - LinkNode struc - NextNode dw ? - Value dw ? - Text db MAX_TEXT_LENGTH+1 dup(?) - ;*** Any number of additional data fields may by present *** - LinkNode ends +```nasm +; C near-callable assembly function for inserting a new node in a +; linked list sorted by ascending order of the Value field. The list +; is circular; that is, it has a dummy node as both the head and the +; tail of the list. The dummy node is a sentinel, containing the +; largest possible Value field setting. Tested with TASM. +MAX_TEXT_LENGTH equ 100 ;longest allowed Text field +SENTINEL equ 32767 ;largest possible Value field +LinkNode struc +NextNode dw ? +Value dw ? +Text db MAX_TEXT_LENGTH+1 dup(?) +;*** Any number of additional data fields may by present *** +LinkNode ends - .model small - .code + .model small + .code - ; Inserts the specified node into a ascending-value-sorted linked - ; list, such that value-sorting is maintained. Returns a pointer to - ; the node after which the new node is inserted. - ; C near-callable as: - ; struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode, - ; struct LinkNode *NodeToInsert) - parms struc - dw 2 dup (?) ;pushed return address & BP - HeadOfListNode dw ? ;pointer to head node of list - NodeToInsert dw ? ;pointer to node to insert - parms ends +; Inserts the specified node into a ascending-value-sorted linked +; list, such that value-sorting is maintained. Returns a pointer to +; the node after which the new node is inserted. +; C near-callable as: +; struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode, +; struct LinkNode *NodeToInsert) +parms struc + dw 2 dup (?) ;pushed return address & BP +HeadOfListNode dw ? ;pointer to head node of list +NodeToInsert dw ? ;pointer to node to insert +parms ends - public _InsertNodeSorted - _InsertNodeSorted proc near - push bp - mov bp,sp ;point to stack frame - push si ;preserve register vars - push di - mov si,[bp].NodeToInsert ;point to node to insert - mov ax,[si].Value ;search value - mov di,[bp].HeadOfListNode ;point to linked list in - ; which to insert - SearchLoop: - mov bx,di ;advance to the next node - mov di,[bx].NextNode ;point to following node - cmp [di].Value,ax ;is the following node's - ; value less than the value - ; from the node to insert? - jl SearchLoop ;yes, so continue searching - ;no, so we have found our - ; insert point - mov ax,[bx].NextNode ;link the new node between - mov [si].NextNode,ax ; the current node and the - mov [bx].NextNode,si ; following node - mov ax,bx ;return pointer to node - ; after which we inserted - pop di ;restore register vars - pop si - pop bp - ret - _InsertNodeSorted endp - end + public _InsertNodeSorted +_InsertNodeSorted proc near + push bp + mov bp,sp ;point to stack frame + push si ;preserve register vars + push di + mov si,[bp].NodeToInsert ;point to node to insert + mov ax,[si].Value ;search value + mov di,[bp].HeadOfListNode ;point to linked list in + ; which to insert +SearchLoop: + mov bx,di ;advance to the next node + mov di,[bx].NextNode ;point to following node + cmp [di].Value,ax ;is the following node's + ; value less than the value + ; from the node to insert? + jl SearchLoop ;yes, so continue searching + ;no, so we have found our + ; insert point + mov ax,[bx].NextNode ;link the new node between + mov [si].NextNode,ax ; the current node and the + mov [bx].NextNode,si ; following node + mov ax,bx ;return pointer to node + ; after which we inserted + pop di ;restore register vars + pop si + pop bp + ret +_InsertNodeSorted endp + end +``` **LISTING 15.8 L15-8.C** - /* Sample linked list program. Tested with Borland C++. */ - #include - #include - #include - #include - #include - #include "llist.h" +```c +/* Sample linked list program. Tested with Borland C++. */ +#include +#include +#include +#include +#include +#include "llist.h" - void main() - { int Done = 0, Char, TempValue; - struct LinkNode *TempPtr, *ListPtr, *TempPtr2; - char TempBuffer[MAX_TEXT_LENGTH+3]; +void main() +{ int Done = 0, Char, TempValue; + struct LinkNode *TempPtr, *ListPtr, *TempPtr2; + char TempBuffer[MAX_TEXT_LENGTH+3]; - if ((ListPtr = InitLinkedList()) == NULL) { - printf("Out of memory\n"); - exit(1); - } - while (!Done) { - printf("\nA=add; D=delete; F=find; L=list all; Q=quit\n>"); - Char = toupper(getche()); - printf("\n"); - switch (Char) { - case 'A': /* add a node */ - if ((TempPtr = malloc(sizeof(struct LinkNode))) == NULL) - { - printf("Out of memory\n ); - exit(1); - } - printf("Node value: "); - scanf("%d", &TempPtr->Value); - if ((FindNodeBeforeValue(ListPtr,TempPtr->Value))!=NULL) - { printf("*** value already in list; try again ***\n"); - free(TempPtr); - } else {printf("Node text: "); - TempBuffer[0] = MAX_TEXT_LENGTH; - cgets(TempBuffer); - strcpy(TempPtr->Text, &TempBuffer[2]); - InsertNodeSorted(ListPtr, TempPtr); - printf("\n"); - } - break; - case 'D': /* delete a node */ - 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") - break; - case 'F': /* find a node */ - printf("Value field of node to find: "); - scanf("%d", &TempValue); - if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue)) - != NULL) - printf("Value: %d\nText: %s\n", - TempPtr->NextNode->Value, TempPtr->NextNode->Text); - else - 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"); - } else { - do {printf("Value: %d\n Text: %s\n", TempPtr->Value, - TempPtr->Text); - TempPtr = TempPtr->NextNode; - } while (TempPtr != ListPtr); - } - break; - case 'Q': - Done = 1; - break; - default: - break; - } - } - } + if ((ListPtr = InitLinkedList()) == NULL) { + printf("Out of memory\n"); + exit(1); + } + while (!Done) { + printf("\nA=add; D=delete; F=find; L=list all; Q=quit\n>"); + Char = toupper(getche()); + printf("\n"); + switch (Char) { + case 'A': /* add a node */ + if ((TempPtr = malloc(sizeof(struct LinkNode))) == NULL) + { + printf("Out of memory\n ); + exit(1); + } + printf("Node value: "); + scanf("%d", &TempPtr->Value); + if ((FindNodeBeforeValue(ListPtr,TempPtr->Value))!=NULL) + { printf("*** value already in list; try again ***\n"); + free(TempPtr); + } else {printf("Node text: "); + TempBuffer[0] = MAX_TEXT_LENGTH; + cgets(TempBuffer); + strcpy(TempPtr->Text, &TempBuffer[2]); + InsertNodeSorted(ListPtr, TempPtr); + printf("\n"); + } + break; + case 'D': /* delete a node */ + 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") + break; + case 'F': /* find a node */ + printf("Value field of node to find: "); + scanf("%d", &TempValue); + if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue)) + != NULL) + printf("Value: %d\nText: %s\n", + TempPtr->NextNode->Value, TempPtr->NextNode->Text); + else + 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"); + } else { + do {printf("Value: %d\n Text: %s\n", TempPtr->Value, + TempPtr->Text); + TempPtr = TempPtr->NextNode; + } while (TempPtr != ListPtr); + } + break; + case 'Q': + Done = 1; + break; + default: + break; + } + } +} +``` ### Hi/Lo in 24 Bytes {#Heading6} @@ -182,33 +186,35 @@ reads closely enough. **LISTING 15.9 L15-9.ASM** - ; Find the greatest or smallest unsigned int. - ; C callable (small model); 24 bytes. - ; By David Stafford. - ; unsigned hi( int num, unsigned a[] ); - ; unsigned lo( int num, unsigned a[] ); +```nasm +; Find the greatest or smallest unsigned int. +; C callable (small model); 24 bytes. +; By David Stafford. +; unsigned hi( int num, unsigned a[] ); +; unsigned lo( int num, unsigned a[] ); - public _hi, _lo + public _hi, _lo - _hi: db 0b9h ;mov cx,immediate - _lo: xor cx,cx - pop ax ;get return address - pop dx ;get count - pop bx ;get pointer - push bx ;restore pointer - push dx ;restore count - push ax ;restore return address - save: mov ax,[bx] - top: cmp ax,[bx] - jcxz around - cmc - around: ja save - inc bx - inc bx - dec dx - jnz top +_hi: db 0b9h ;mov cx,immediate +_lo: xor cx,cx + pop ax ;get return address + pop dx ;get count + pop bx ;get pointer + push bx ;restore pointer + push dx ;restore count + push ax ;restore return address +save: mov ax,[bx] +top: cmp ax,[bx] + jcxz around + cmc +around: ja save + inc bx + inc bx + dec dx + jnz top - ret + 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 diff --git a/16-01.md b/16-01.md index cac8a69..9452e26 100644 --- a/16-01.md +++ b/16-01.md @@ -98,75 +98,77 @@ timed from a RAM disk on a 20 MHz 386. **LISTING 16.1 L16-1.C** - /* Word-counting program. Tested with Borland C++ in C - compilation mode and the small model. */ - - #include - #include - #include - #include - #include - - #define B UFFER_SIZE 0x8000 /* largest chunk of file worked - with at any one time */ - int main(int, char **); - - int main(int argc, char **argv) { - int Handle; - unsigned int BlockSize; - long FileSize; - unsigned long WordCount = 0; - char *Buffer, CharFlag = 0, PredCharFlag, *BufferPtr, Ch; - - if (argc != 2) { - printf("usage: wc \n"); - exit(1); - } - - if ((Buffer = malloc(BUFFER_SIZE)) == NULL) { - 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]); - exit(1); - } - - if ((FileSize = filelength(Handle)) == -1) { - printf("Error sizing file %s\n", argv[1]); - exit(1); - } - - /* Process the file in chunks */ - while (FileSize > 0) { - /* Get the next chunk */ - FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE)); - if (read(Handle, Buffer, BlockSize) == -1) { - printf("Error reading file %s\n", argv[1]); - exit(1); - } - /* Count words in the chunk */ - BufferPtr = Buffer; - do { - PredCharFlag = CharFlag; - 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 == ‘\''); - if ((!CharFlag) && PredCharFlag) { - WordCo u nt++; - } - } while (—BlockSize); - } - - /* Catch the last word, if any */ - if (CharFlag) { - WordCount++; - } - printf("\nTotal words in file: %lu\n", WordCount); - return(0); - } +```c + /* Word-counting program. Tested with Borland C++ in C + compilation mode and the small model. */ + + #include + #include + #include + #include + #include + + #define B UFFER_SIZE 0x8000 /* largest chunk of file worked + with at any one time */ + int main(int, char **); + + int main(int argc, char **argv) { + int Handle; + unsigned int BlockSize; + long FileSize; + unsigned long WordCount = 0; + char *Buffer, CharFlag = 0, PredCharFlag, *BufferPtr, Ch; + + if (argc != 2) { + printf("usage: wc \n"); + exit(1); + } + + if ((Buffer = malloc(BUFFER_SIZE)) == NULL) { + 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]); + exit(1); + } + + if ((FileSize = filelength(Handle)) == -1) { + printf("Error sizing file %s\n", argv[1]); + exit(1); + } + + /* Process the file in chunks */ + while (FileSize > 0) { + /* Get the next chunk */ + FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE)); + if (read(Handle, Buffer, BlockSize) == -1) { + printf("Error reading file %s\n", argv[1]); + exit(1); + } + /* Count words in the chunk */ + BufferPtr = Buffer; + do { + PredCharFlag = CharFlag; + 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 == ‘\''); + if ((!CharFlag) && PredCharFlag) { + WordCo u nt++; + } + } while (—BlockSize); + } + + /* Catch the last word, if any */ + if (CharFlag) { + WordCount++; + } + printf("\nTotal words in file: %lu\n", WordCount); + return(0); + } +``` diff --git a/16-02.md b/16-02.md index 9e4d5ca..ded3f45 100644 --- a/16-02.md +++ b/16-02.md @@ -22,147 +22,151 @@ generates. **LISTING 16.2 L16-2.C** - /* Word-counting program incorporating assembly language. Tested - with Borland C++ in C compilation mode & the small model. */ - - #include - #include - #include - #include - #include - - #define BUFFER_SIZE 0x8000 /* largest chunk of file worked - with at any one time */ - int main(int, char **); - void ScanBuffer(char *, unsigned int, char *, unsigned long *); - - int main(int argc, char **argv) { - int Handle; - unsigned int BlockSize; - long FileSize; - unsigned long WordCount = 0; - char *Buffer, CharFlag = 0; - - if (argc != 2) { - printf("usage: wc \n"); - exit(1); - } - - if ((Buffer = malloc(BUFFER_SIZE)) == NULL) { - 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]); - exit(1); - } - - if ((FileSize = filelength(Handle)) == -1) { - printf("Error sizing file %s\n", argv[1]); - exit(1); - } - - CharFlag = 0; - while (FileSize > 0) { - FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE)); - if (read(Handle, Buffer, BlockSize) == -1) { - printf("Error reading file %s\n", argv[1]); - exit(1); - } - ScanBuffer(Buffer, BlockSize, &CharFlag, &WordCount); - } - - /* Catch the last word, if any */ - if (CharFlag) { - WordCount++; - } - printf("\nTotal words in file: %lu\n", WordCount); - return(0); - } +```c +/* Word-counting program incorporating assembly language. Tested + with Borland C++ in C compilation mode & the small model. */ + +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 0x8000 /* largest chunk of file worked + with at any one time */ +int main(int, char **); +void ScanBuffer(char *, unsigned int, char *, unsigned long *); + +int main(int argc, char **argv) { + int Handle; + unsigned int BlockSize; + long FileSize; + unsigned long WordCount = 0; + char *Buffer, CharFlag = 0; + + if (argc != 2) { + printf("usage: wc \n"); + exit(1); + } + + if ((Buffer = malloc(BUFFER_SIZE)) == NULL) { + 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]); + exit(1); + } + + if ((FileSize = filelength(Handle)) == -1) { + printf("Error sizing file %s\n", argv[1]); + exit(1); + } + + CharFlag = 0; + while (FileSize > 0) { + FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE)); + if (read(Handle, Buffer, BlockSize) == -1) { + printf("Error reading file %s\n", argv[1]); + exit(1); + } + ScanBuffer(Buffer, BlockSize, &CharFlag, &WordCount); + } + + /* Catch the last word, if any */ + if (CharFlag) { + WordCount++; + } + printf("\nTotal words in file: %lu\n", WordCount); + return(0); +} +``` **LISTING 16.3 L16-3.ASM** - ; Assembly subroutine for Listing 16.2. Scans through Buffer, of - ; length BufferLength, counting words and updating WordCount as - ; appropriate. BufferLength must be > 0. *CharFlag and *WordCount - ; should equal 0 on the first call. Tested with TASM. - ; C near-callable as: - ; void ScanBuffer(char *Buffer, unsigned int BufferLength, - ; char *CharFlag, unsigned long *WordCount); - - parms struc - dw 2 dup(?) ;pushed return address & BP - Buffer dw ? ;buffer to scan - BufferLength dw ? ;length of buffer to scan - CharFlag dw ? ;pointer to flag for state of last - ; char processed on entry (0 on - ; initial call). Updated on exit - WordCount dw ? ;pointer to 32-bit count of words - ; found (0 on initial call) - parms ends - - .model small - .code - public _ScanBuffer - _ScanBuffer proc near - push bp ;preserve caller's stack frame - mov bp,sp ;set up local stack frame - push si ;preserve caller's register vars - push di - - mov si,[bp+Buffer] ;point to buffer to scan - mov bx,[bp+WordCount] - mov cx,[bx] ;get current 32-bit word count - mov dx,[bx+2] - mov bx,[bp+CharFlag] - mov bl,[bx] ;get current CharFlag - mov di,[bp+BufferLength];get # of bytes to scan - ScanLoop: - mov bh,bl ;PredCharFlag = CharFlag; - lodsb ;Ch = *BufferPtr++ & 0x7F; - 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 - jb CheckAZ - cmp al,‘z' - jna IsAChar - CheckAZ: - cmp al,‘A' ;it is a char if between A and Z - jb Check09 - cmp al,‘Z' - jna IsAChar - Check09: - cmp al,‘0' ;it is a char if between 0 and 9 - jb CheckApostrophe - cmp al,‘9' - jna IsAChar - CheckApostrophe: - cmp al,27h ;it is a char if an apostrophe - jz IsAChar - sub bl,bl ;not a char; CharFlag = 0; - and bh,bh - jz ScanLoopBottom ;if ((!CharFlag) && PredCharFlag) { - add cx,1 ; (WordCount)++; - adc dx,0 ;} - IsAChar: - ScanLoopBottom: - dec di ;} while (—BufferLength); - jnz ScanLoop - - mov si,[bp+CharFlag] - mov [si],bl ;set new CharFlag - mov bx,[bp+WordCount] - mov [bx],cx ;set new word count - mov [bx+2],dx - - pop di ;restore caller's register vars - pop si - pop bp ;restore caller's stack frame - ret - _ScanBuffer endp - end +```nasm +; Assembly subroutine for Listing 16.2. Scans through Buffer, of +; length BufferLength, counting words and updating WordCount as +; appropriate. BufferLength must be > 0. *CharFlag and *WordCount +; should equal 0 on the first call. Tested with TASM. +; C near-callable as: +; void ScanBuffer(char *Buffer, unsigned int BufferLength, +; char *CharFlag, unsigned long *WordCount); + +parms struc + dw 2 dup(?) ;pushed return address & BP +Buffer dw ? ;buffer to scan +BufferLength dw ? ;length of buffer to scan +CharFlag dw ? ;pointer to flag for state of last + ; char processed on entry (0 on + ; initial call). Updated on exit +WordCount dw ? ;pointer to 32-bit count of words + ; found (0 on initial call) +parms ends + + .model small + .code + public _ScanBuffer +_ScanBuffer proc near + push bp ;preserve caller's stack frame + mov bp,sp ;set up local stack frame + push si ;preserve caller's register vars + push di + + mov si,[bp+Buffer] ;point to buffer to scan + mov bx,[bp+WordCount] + mov cx,[bx] ;get current 32-bit word count + mov dx,[bx+2] + mov bx,[bp+CharFlag] + mov bl,[bx] ;get current CharFlag + mov di,[bp+BufferLength];get # of bytes to scan +ScanLoop: + mov bh,bl ;PredCharFlag = CharFlag; + lodsb ;Ch = *BufferPtr++ & 0x7F; + 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 + jb CheckAZ + cmp al,‘z' + jna IsAChar +CheckAZ: + cmp al,‘A' ;it is a char if between A and Z + jb Check09 + cmp al,‘Z' + jna IsAChar +Check09: + cmp al,‘0' ;it is a char if between 0 and 9 + jb CheckApostrophe + cmp al,‘9' + jna IsAChar +CheckApostrophe: + cmp al,27h ;it is a char if an apostrophe + jz IsAChar + sub bl,bl ;not a char; CharFlag = 0; + and bh,bh + jz ScanLoopBottom ;if ((!CharFlag) && PredCharFlag) { + add cx,1 ; (WordCount)++; + adc dx,0 ;} +IsAChar: +ScanLoopBottom: + dec di ;} while (—BufferLength); + jnz ScanLoop + + mov si,[bp+CharFlag] + mov [si],bl ;set new CharFlag + mov bx,[bp+WordCount] + mov [bx],cx ;set new word count + mov [bx+2],dx + + pop di ;restore caller's register vars + pop si + pop bp ;restore caller's stack frame + ret +_ScanBuffer endp + end +``` #### Which Way to Go from Here? {#Heading4} diff --git a/16-03.md b/16-03.md index 15abd41..e02807e 100644 --- a/16-03.md +++ b/16-03.md @@ -12,97 +12,98 @@ pages: 303-305 **LISTING 16.4 L16-4.ASM** - ; Assembly subroutine for Listing 16.2. Scans through Buffer, of - ; length BufferLength, counting words and updating WordCount as - ; appropriate, using a lookup table-based approach. BufferLength - ; must be > 0. *CharFlag and *WordCount should equal 0 on the - ; first call. Tested with TASM. - ; C near-callable as: - ; void ScanBuffer(char *Buffer, unsigned int BufferLength, - ; char *CharFlag, unsigned long *WordCount); - - parms struc - dw 2 dup(?) ;pushed return address & BP - Buffer dw ? ;buffer to scan - BufferLength dw ? ;length of buffer to scan - CharFlag dw ? ;pointer to flag for state of last - ;char processed on entry (0 on - ;initial call). Updated on exit - WordCount dw ? ;pointer to 32-bit count of words - ; found (0 on initial call) - parms ends - - .model small - .data - ; Table of char/not statuses for byte values 0-255 (128-255 are - ; duplicates of 0-127 to effectively mask off bit 7, which some - ; word processors set as an internal flag). - CharStatusTable label byte - REPT 2 - db 39 dup(0) - db 1 ;apostrophe - db 8 dup(0) - db 10 dup(1) ;0-9 - db 7 dup(0) - db 26 dup(1) ;A-Z - db 6 dup(0) - db 26 dup(1) ;a-z - db 5 dup(0) - ENDM - - .code - public _ScanBuffer - _ScanBuffer proc near - push bp ;preserve caller's stack frame - mov bp,sp ;set up local stack frame - push si ;preserve caller's register vars - push di - - mov si,[bp+Buffer] ;point to buffer to scan - mov bx,[bp+WordCount] - mov di,[bx] ;get current 32-bit word count - mov dx,[bx+2] - mov bx,[bp+CharFlag] - mov al,[bx] ;get current CharFlag - mov cx,[bp+BufferLength] ;get # of bytes to scan - mov bx,offset CharStatusTable - ScanLoop: - and al,al ;ZF=0 if last byte was a char, - ; ZF=1 if not - lodsb ;get the next byte - ;***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 - ; not a character - and al,al ;last byte was a character; is the - ; current byte a character? - jz CountWord ;no, so count a word - ScanLoopBottom: - dec cx ;count down buffer length - jnz ScanLoop - Done: - mov si,[bp+CharFlag] - mov [si],al ;set new CharFlag - mov bx,[bp+WordCount] - mov [bx],di ;set new word count - mov [bx+2],dx - - pop di ;restore caller's register vars - pop si - pop bp ;restore caller's stack frame - ret - - align 2 - CountWord: - add di,1 ;increment the word count - adc dx,0 - dec cx ;count down buffer length - jnz ScanLoop - jmp Done - _ScanBuffer endp - end - +```nasm +; Assembly subroutine for Listing 16.2. Scans through Buffer, of +; length BufferLength, counting words and updating WordCount as +; appropriate, using a lookup table-based approach. BufferLength +; must be > 0. *CharFlag and *WordCount should equal 0 on the +; first call. Tested with TASM. +; C near-callable as: +; void ScanBuffer(char *Buffer, unsigned int BufferLength, +; char *CharFlag, unsigned long *WordCount); + +parms struc + dw 2 dup(?) ;pushed return address & BP +Buffer dw ? ;buffer to scan +BufferLength dw ? ;length of buffer to scan +CharFlag dw ? ;pointer to flag for state of last + ;char processed on entry (0 on + ;initial call). Updated on exit +WordCount dw ? ;pointer to 32-bit count of words + ; found (0 on initial call) +parms ends + + .model small + .data +; Table of char/not statuses for byte values 0-255 (128-255 are +; duplicates of 0-127 to effectively mask off bit 7, which some +; word processors set as an internal flag). +CharStatusTable label byte + REPT 2 + db 39 dup(0) + db 1 ;apostrophe + db 8 dup(0) + db 10 dup(1) ;0-9 + db 7 dup(0) + db 26 dup(1) ;A-Z + db 6 dup(0) + db 26 dup(1) ;a-z + db 5 dup(0) + ENDM + + .code + public _ScanBuffer +_ScanBuffer proc near + push bp ;preserve caller's stack frame + mov bp,sp ;set up local stack frame + push si ;preserve caller's register vars + push di + + mov si,[bp+Buffer] ;point to buffer to scan + mov bx,[bp+WordCount] + mov di,[bx] ;get current 32-bit word count + mov dx,[bx+2] + mov bx,[bp+CharFlag] + mov al,[bx] ;get current CharFlag + mov cx,[bp+BufferLength] ;get # of bytes to scan + mov bx,offset CharStatusTable +ScanLoop: + and al,al ;ZF=0 if last byte was a char, + ; ZF=1 if not + lodsb ;get the next byte + ;***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 + ; not a character + and al,al ;last byte was a character; is the + ; current byte a character? + jz CountWord ;no, so count a word +ScanLoopBottom: + dec cx ;count down buffer length + jnz ScanLoop +Done: + mov si,[bp+CharFlag] + mov [si],al ;set new CharFlag + mov bx,[bp+WordCount] + mov [bx],di ;set new word count + mov [bx+2],dx + + pop di ;restore caller's register vars + pop si + pop bp ;restore caller's stack frame + ret + + align 2 +CountWord: + add di,1 ;increment the word count + adc dx,0 + dec cx ;count down buffer length + jnz ScanLoop + jmp Done +_ScanBuffer endp + end +``` Listing 16.4 features several interesting tricks. First, it uses **LODSB** and **XLAT** in succession, a very neat way to get a diff --git a/16-04.md b/16-04.md index a2defab..cf56bca 100644 --- a/16-04.md +++ b/16-04.md @@ -40,9 +40,9 @@ 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: - - CMP DH,[EBX+EAX] - +```nasm +CMP DH,[EBX+EAX] +``` Harmless enough, save for two things. First, EBX happened to be zero at this point (a leftover from an earlier version of the code, as it turned diff --git a/16-05.md b/16-05.md index d3f4607..e0226cd 100644 --- a/16-05.md +++ b/16-05.md @@ -36,181 +36,183 @@ Table: Table 16.2 The top four word-counting entries. **LISTING 16.5 QSCAN3.ASM** - ; QSCAN3.ASM - ; David Stafford - - - COMMENT $ - - How it works - —————— - The idea is to go through the buffer fetching each letter-pair (words - rather than bytes). The carry flag indicates whether we are - currently in a (text) word or not. The letter-pair fetched from the - buffer is converted to a 16-bit address by shifting it left one bit - (losing the high bit of the second character) and putting the carry - flag in the low bit. The high bit of the count register is set to - 1. Then the count register is added to the byte found at the given - address in a large (64K, naturally) table. The byte at the given - address will contain a 1 in the high bit if the last character of the - letter-pair is a word-letter (alphanumeric or apostrophe). This will - set the carry flag since the high bit of the count register is also a - 1. The low bit of the byte found at the given address will be one if - the second character of the previous letter-pair was a word-letter - and the first character of this letter-pair is not a word-letter. It - will also be 1 if the first character of this letter-pair is a - word-letter but the second character is not. This process is - repeated. Finally, the carry flag is saved to indicate the final - 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! - - The beauty of this method is that no jumps are required, the - operations are fast, it requires only one table and the process can - be repeated (unrolled) many times. QSCAN3 can read 256 bytes without - jumping. - - COMMEND $ - .model small - .code - - Test1 macro x,y ;9 or 10 bytes - Addr&x: mov di,[bp+y] ;3 or 4 bytes - adc di,di - or ax,si - add al,[di] - endm - - Test2 macro x,y ;7 or 8 bytes - Addr&x: mov di,[bp+y] ;3 or 4 bytes - adc di,di - add ah,[di] - endm - - Scan = 128 ;scan 256 bytes at a time - Buffer = 4 ;parms - BufferLength = 6 - CharFlag = 8 - WordCount = 10 - - public _ScanBuffer - _ScanBuffer proc near - push bp - mov bp,sp - push si - push di - - xor cx,cx - mov si,[bp+Buffer] ;si = text buffer - mov ax,[bp+BufferLength] ;dx = length in bytes - shr ax,1 ;dx = length in words - jnz NormalBuf - OneByteBuf: - mov ax,seg WordTable - mov es,ax - - mov di,[bp+CharFlag] - mov bh,[di] ;bh = old CharFlag - mov bl,[si] ;bl = 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) - shr al,1 ;get low bit - adc cx,cx ;cx = 0 or 1 - xchg ax,bx - jmp CleanUp - NormalBuf: - push bp ;(1) - pushf ;(2) - - cwd ;dx = 0 - mov cl,Scan - div cx - or dx,dx ;remainder? - jz StartAtTheTop ;nope, do the whole banana - sub cx,dx - sub si,cx ;adjust buf pointer - sub si,cx - inc ax ;adjust for partial read - - StartAtTheTop: mov bx,dx ;get index for start... - shl bx,1 - mov di,LoopEntry[bx] ;...address in di - xchg dx,ax ;dx is the loop counter - xor cx,cx ;total word count - mov bx,[bp+CharFlag] - mov bl,[bx] ;bl = old CharFlag - mov bp,seg WordTable - mov ds,bp - mov bp,si ;scan buffer with bp - mov si,8080h ;hi bits - mov ax,si ;init local word counter - shr bl,1 ;carry = old CharFlag - jmp di - - align 2 - Top: add bx,bx ;restore carry - n = 0 - rept Scan/2 - Test1 %n,%n*2 - Test2 %n+1,%n*2+2 - n = n+2 - endm - EndCount: - sbb bx,bx ;save carry - if Scan ge 128 ;because al+ah may equal 128! - or ax,si - add al,ah - mov ah,0 - else - add al,ah - and ax,7fh ;mask - endif - add cx,ax ;update word count - mov ax,si - add bp,Scan*2 - dec dx ;any left? - jng Quit - jmp Top - - Quit: popf ;(2) even or odd buffer? - jnc ItsEven - clc - Test1 Odd,-1 - sbb bx,bx ;save carry - shr ax,1 - adc cx,0 - ItsEven: - push ss ;restore ds - pop ds - pop bp ;(1) - CleanUp: - mov si,[bp+WordCount] - add [si],cx - adc word ptr [si+2],0 - and bh,1 ;save only the carry flag - mov si,[bp+CharFlag] - mov [si],bh - pop di - pop si - pop bp - ret - _ScanBuffer endp - - .data - Address macro X - dw Addr&X - endm - - LoopEntry label word - n = Scan - REPT Scan - Address %n MOD Scan - n = n - 1 - ENDM - - .fardata WordTable - include qscan3.inc ;built by MAKETAB - end +```nasm + ; QSCAN3.ASM + ; David Stafford + + + COMMENT $ + +How it works +—————— +The idea is to go through the buffer fetching each letter-pair (words +rather than bytes). The carry flag indicates whether we are +currently in a (text) word or not. The letter-pair fetched from the +buffer is converted to a 16-bit address by shifting it left one bit +(losing the high bit of the second character) and putting the carry +flag in the low bit. The high bit of the count register is set to +1. Then the count register is added to the byte found at the given +address in a large (64K, naturally) table. The byte at the given +address will contain a 1 in the high bit if the last character of the +letter-pair is a word-letter (alphanumeric or apostrophe). This will +set the carry flag since the high bit of the count register is also a +1. The low bit of the byte found at the given address will be one if +the second character of the previous letter-pair was a word-letter +and the first character of this letter-pair is not a word-letter. It +will also be 1 if the first character of this letter-pair is a +word-letter but the second character is not. This process is +repeated. Finally, the carry flag is saved to indicate the final +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! + +The beauty of this method is that no jumps are required, the +operations are fast, it requires only one table and the process can +be repeated (unrolled) many times. QSCAN3 can read 256 bytes without +jumping. + + COMMEND $ + .model small + .code + + Test1 macro x,y ;9 or 10 bytes + Addr&x: mov di,[bp+y] ;3 or 4 bytes + adc di,di + or ax,si + add al,[di] + endm + + Test2 macro x,y ;7 or 8 bytes + Addr&x: mov di,[bp+y] ;3 or 4 bytes + adc di,di + add ah,[di] + endm + + Scan = 128 ;scan 256 bytes at a time + Buffer = 4 ;parms + BufferLength = 6 + CharFlag = 8 + WordCount = 10 + + public _ScanBuffer + _ScanBuffer proc near + push bp + mov bp,sp + push si + push di + + xor cx,cx + mov si,[bp+Buffer] ;si = text buffer + mov ax,[bp+BufferLength] ;dx = length in bytes + shr ax,1 ;dx = length in words + jnz NormalBuf + OneByteBuf: + mov ax,seg WordTable + mov es,ax + + mov di,[bp+CharFlag] + mov bh,[di] ;bh = old CharFlag + mov bl,[si] ;bl = 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) + shr al,1 ;get low bit + adc cx,cx ;cx = 0 or 1 + xchg ax,bx + jmp CleanUp + NormalBuf: + push bp ;(1) + pushf ;(2) + + cwd ;dx = 0 + mov cl,Scan + div cx + or dx,dx ;remainder? + jz StartAtTheTop ;nope, do the whole banana + sub cx,dx + sub si,cx ;adjust buf pointer + sub si,cx + inc ax ;adjust for partial read + + StartAtTheTop: mov bx,dx ;get index for start... + shl bx,1 + mov di,LoopEntry[bx] ;...address in di + xchg dx,ax ;dx is the loop counter + xor cx,cx ;total word count + mov bx,[bp+CharFlag] + mov bl,[bx] ;bl = old CharFlag + mov bp,seg WordTable + mov ds,bp + mov bp,si ;scan buffer with bp + mov si,8080h ;hi bits + mov ax,si ;init local word counter + shr bl,1 ;carry = old CharFlag + jmp di + + align 2 + Top: add bx,bx ;restore carry + n = 0 + rept Scan/2 + Test1 %n,%n*2 + Test2 %n+1,%n*2+2 + n = n+2 + endm + EndCount: + sbb bx,bx ;save carry + if Scan ge 128 ;because al+ah may equal 128! + or ax,si + add al,ah + mov ah,0 + else + add al,ah + and ax,7fh ;mask + endif + add cx,ax ;update word count + mov ax,si + add bp,Scan*2 + dec dx ;any left? + jng Quit + jmp Top + + Quit: popf ;(2) even or odd buffer? + jnc ItsEven + clc + Test1 Odd,-1 + sbb bx,bx ;save carry + shr ax,1 + adc cx,0 + ItsEven: + push ss ;restore ds + pop ds + pop bp ;(1) + CleanUp: + mov si,[bp+WordCount] + add [si],cx + adc word ptr [si+2],0 + and bh,1 ;save only the carry flag + mov si,[bp+CharFlag] + mov [si],bh + pop di + pop si + pop bp + ret + _ScanBuffer endp + + .data + Address macro X + dw Addr&X + endm + + LoopEntry label word + n = Scan + REPT Scan + Address %n MOD Scan + n = n - 1 + ENDM + + .fardata WordTable + include qscan3.inc ;built by MAKETAB + end +``` diff --git a/16-07.md b/16-07.md index e57e0fd..53532ed 100644 --- a/16-07.md +++ b/16-07.md @@ -12,110 +12,112 @@ pages: 313-316 **Listing 16.6 OPT2.ASM** - ; - ; Opt2 Final optimization word count - ; Written by Michael Abrash - ; Modified by Willem Clements - ; C/ Moncayo 5, Laurel de la Reina - ; 18140 La Zubia - ; Granada, Spain - ; Tel 34-58-890398 - ; Fax 34-58-224102 - ; - parms struc - dw 2 dup(?) - buffer dw ? - bufferlength dw ? - charflag dw ? - wordcount dw ? - parms ends - .model small - .data - charstatustable label byte - rept 2 - db 39 dup(0) - db 1 - db 8 dup(0) - db 10 dup(1) - db 7 dup(0) - db 26 dup(1) - db 6 dup(0) - db 26 dup(1) - db 5 dup(0) - endm - .code - public _ScanBuffer - _ScanBuffer proc near - push bp - mov bp,sp - push si - push di - mov si,[bp+buffer] - mov bx,[bp+charflag] - mov al,[bx] - mov cx,[bp+bufferlength] - mov bx,offset charstatustable - xor di,di ; set wordcount to zero - shr cx,1 ; change count to wordcount - jc oddentry ; odd number of bytes to process - cmp al,01h ; check if last one is char - jne scanloop4 ; if not so, search for char - jmp scanloop1 ; if so, search for zero - oddentry: xchg al,ah ; last one in ah - lodsb ; get first byte - inc cx - cmp ah,01h ; check if last one was char - jne scanloop5 ; if not so, search for char - jmp scanloop2 ; if so, search for zero - ; - ; locate the end of a word - scanloop1: lodsw ; get two chars - xlat ; translate first - xchg al,ah ; first in ah - scanloop2: xlat ; translate second - dec cx ; count down - jz done1 ; no more bytes left - cmp ax,0101h ; check if two chars - je scanloop1 ; go for next two bytes - inc di ; increase wordcount - cmp al,01h ; check if new word started - je scanloop1 ; locate end of word - ; - ; locate the begin of a word - scanloop4: lodsw ; get two chars - xlat ; translate first - xchg al,ah ; first in ah - scanloop5: xlat ; translate second - dec cx ; count down - jz done2 ; no more bytes left - cmp ax,0 ; check if word started - je scanloop4 ; if not, locate begin - cmp al,01h ; check one-letter word - je scanloop1 ; if not, locate end of word - inc di ; increase wordcount - jmp scanloop4 ; locate begin of next word - done1: cmp ax,0101h ; check if end-of-word - je done ; if not, we have finished - inc di ; increase wordcount - jmp done - done2: cmp ax,0100h ; check for one-letter word - jne done ; if not, we have finished - inc di ; increase wordcount - done: mov si,[bp+charflag] - mov [si],al - mov bx,[bp+wordcount] - mov ax,[bx] - mov dx,[bx+2] - add di,ax - adc dx,0 - mov [bx],di - mov [bx+2],dx - pop di - pop si - pop bp - ret - _ScanBuffer endp - end +```nasm +; +; Opt2 Final optimization word count +; Written by Michael Abrash +; Modified by Willem Clements +; C/ Moncayo 5, Laurel de la Reina +; 18140 La Zubia +; Granada, Spain +; Tel 34-58-890398 +; Fax 34-58-224102 +; +parms struc + dw 2 dup(?) +buffer dw ? +bufferlength dw ? +charflag dw ? +wordcount dw ? +parms ends + .model small + .data +charstatustable label byte + rept 2 + db 39 dup(0) + db 1 + db 8 dup(0) + db 10 dup(1) + db 7 dup(0) + db 26 dup(1) + db 6 dup(0) + db 26 dup(1) + db 5 dup(0) + endm + .code + public _ScanBuffer +_ScanBuffer proc near + push bp + mov bp,sp + push si + push di + mov si,[bp+buffer] + mov bx,[bp+charflag] + mov al,[bx] + mov cx,[bp+bufferlength] + mov bx,offset charstatustable + xor di,di ; set wordcount to zero + shr cx,1 ; change count to wordcount + jc oddentry ; odd number of bytes to process + cmp al,01h ; check if last one is char + jne scanloop4 ; if not so, search for char + jmp scanloop1 ; if so, search for zero +oddentry: xchg al,ah ; last one in ah + lodsb ; get first byte + inc cx + cmp ah,01h ; check if last one was char + jne scanloop5 ; if not so, search for char + jmp scanloop2 ; if so, search for zero +; +; locate the end of a word +scanloop1: lodsw ; get two chars + xlat ; translate first + xchg al,ah ; first in ah +scanloop2: xlat ; translate second + dec cx ; count down + jz done1 ; no more bytes left + cmp ax,0101h ; check if two chars + je scanloop1 ; go for next two bytes + inc di ; increase wordcount + cmp al,01h ; check if new word started + je scanloop1 ; locate end of word +; +; locate the begin of a word +scanloop4: lodsw ; get two chars + xlat ; translate first + xchg al,ah ; first in ah +scanloop5: xlat ; translate second + dec cx ; count down + jz done2 ; no more bytes left + cmp ax,0 ; check if word started + je scanloop4 ; if not, locate begin + cmp al,01h ; check one-letter word + je scanloop1 ; if not, locate end of word + inc di ; increase wordcount + jmp scanloop4 ; locate begin of next word +done1: cmp ax,0101h ; check if end-of-word + je done ; if not, we have finished + inc di ; increase wordcount + jmp done +done2: cmp ax,0100h ; check for one-letter word + jne done ; if not, we have finished + inc di ; increase wordcount +done: mov si,[bp+charflag] + mov [si],al + mov bx,[bp+wordcount] + mov ax,[bx] + mov dx,[bx+2] + add di,ax + adc dx,0 + mov [bx],di + mov [bx+2],dx + pop di + pop si + pop bp + ret +_ScanBuffer endp + end +``` ### Level 2: A New Perspective {#Heading11} diff --git a/16-08.md b/16-08.md index d98ddf2..06c2961 100644 --- a/16-08.md +++ b/16-08.md @@ -12,20 +12,21 @@ pages: 316-319 **Listing 16.7 L16-7.ASM** - 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 - 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 - add di,dx ;we add 1 for each char/not transition - mov dl,al - dec dx - jnz ScanLoop - +```nasm +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 + 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 + add di,dx ;we add 1 for each char/not transition + mov dl,al + dec dx + jnz ScanLoop +``` John later divides the transition count by two to get the word count. (Food for thought: It's also possible to use **CMP** and **ADC** to @@ -66,39 +67,40 @@ report a "location counter overflow" warning; ignore it.) **LISTING 16.8 MAKETAB.C** - // MAKETAB.C — Build QSCAN3.INC for QSCAN3.ASM - - #include - #include - - #define ChType( c ) (((c) & 0x7f) == ‘\'' || isalnum((c) & 0x7f)) - - int NoCarry[ 4 ] = { 0, 0x80, 1, 0x80 }; - int Carry[ 4 ] = { 1, 0x81, 1, 0x80 }; - - void main( void ) - { - int ahChar, alChar, i; - FILE *t = fopen( "QSCAN3.INC", "wt" ); - - printf( "Building table. Please wait..." ); - - for( ahChar = 0; ahChar < 128; ahChar++ ) - { - for( alChar = 0; alChar < 256; alChar++ ) - { - i = ChType( alChar ) * 2 + ChType( ahChar ); - - if( alChar % 8 == 0 ) fprintf( t, "\ndb %02Xh", NoCarry[ i ] ); - else fprintf( t, ",%02Xh", NoCarry[ i ] ); - - fprintf( t, ",%02Xh", Carry[ i ] ); - } - } - - fclose( t ); - } - +```c +// MAKETAB.C — Build QSCAN3.INC for QSCAN3.ASM + +#include +#include + +#define ChType( c ) (((c) & 0x7f) == ‘\'' || isalnum((c) & 0x7f)) + +int NoCarry[ 4 ] = { 0, 0x80, 1, 0x80 }; +int Carry[ 4 ] = { 1, 0x81, 1, 0x80 }; + +void main( void ) + { + int ahChar, alChar, i; + FILE *t = fopen( "QSCAN3.INC", "wt" ); + + printf( "Building table. Please wait..." ); + + for( ahChar = 0; ahChar < 128; ahChar++ ) + { + for( alChar = 0; alChar < 256; alChar++ ) + { + i = ChType( alChar ) * 2 + ChType( ahChar ); + + if( alChar % 8 == 0 ) fprintf( t, "\ndb %02Xh", NoCarry[ i ] ); + else fprintf( t, ",%02Xh", NoCarry[ i ] ); + + fprintf( t, ",%02Xh", Carry[ i ] ); + } + } + + fclose( t ); + } +``` David's approach is simplicity itself, although his implementation arguably is not. Consider any three sequential bytes in the buffer. diff --git a/17-02.md b/17-02.md index 5bf0b9a..2c5fe6e 100644 --- a/17-02.md +++ b/17-02.md @@ -12,256 +12,260 @@ pages: 325-329 **LISTING 17.1 L17-1.CPP** - /* C++ Game of Life implementation for any mode for which mode set - and draw pixel functions can be provided. - Tested with Borland C++ in the small model. */ - #include - #include - #include - #include - #include - #include - #include - #include +```cpp +/* C++ Game of Life implementation for any mode for which mode set + and draw pixel functions can be provided. + Tested with Borland C++ in the small model. */ +#include +#include +#include +#include +#include +#include +#include +#include - #define ON_COLOR 15 // on-cell pixel color - #define OFF_COLOR 0 // off-cell pixel color - #define MSG_LINE 10 // row for text messages - #define GENERATION_LINE 12 // row for generation # display - #define LIMIT_18_HZ 1 // set 1 for maximum frame rate = 18Hz - #define WRAP_EDGES 1 // set to 0 to disable wrapping around - // at cell map edges - class cellmap { - private: - unsigned char *cells; - unsigned int width; - unsigned int width_in_bytes; - unsigned int height; - unsigned int length_in_bytes; - public: - cellmap(unsigned int h, unsigned int v); - ~cellmap(void); - void copy_cells(cellmap &sourcemap); - void set_cell(unsigned int x, unsigned int y); - void clear_cell(unsigned int x, unsigned int y); - int cell_state(int x, int y); - void next_generation(cellmap& dest_map); - }; +#define ON_COLOR 15 // on-cell pixel color +#define OFF_COLOR 0 // off-cell pixel color +#define MSG_LINE 10 // row for text messages +#define GENERATION_LINE 12 // row for generation # display +#define LIMIT_18_HZ 1 // set 1 for maximum frame rate = 18Hz +#define WRAP_EDGES 1 // set to 0 to disable wrapping around + // at cell map edges +class cellmap { +private: + unsigned char *cells; + unsigned int width; + unsigned int width_in_bytes; + unsigned int height; + unsigned int length_in_bytes; +public: + cellmap(unsigned int h, unsigned int v); + ~cellmap(void); + void copy_cells(cellmap &sourcemap); + void set_cell(unsigned int x, unsigned int y); + void clear_cell(unsigned int x, unsigned int y); + int cell_state(int x, int y); + void next_generation(cellmap& dest_map); +}; - extern void enter_display_mode(void); - extern void exit_display_mode(void); - extern void draw_pixel(unsigned int X, unsigned int Y, - unsigned int Color); - extern void show_text(int x, int y, char *text); +extern void enter_display_mode(void); +extern void exit_display_mode(void); +extern void draw_pixel(unsigned int X, unsigned int Y, + unsigned int Color); +extern void show_text(int x, int y, char *text); - /* Controls the size of the cell map. Must be within the capabilities - of the display mode, and must be limited to leave room for text - display at right. */ - unsigned int cellmap_width = 96; - unsigned int cellmap_height = 96; - /* Width & height in pixels of each cell as displayed on screen. */ - unsigned int magnifier = 2; +/* Controls the size of the cell map. Must be within the capabilities + of the display mode, and must be limited to leave room for text + display at right. */ +unsigned int cellmap_width = 96; +unsigned int cellmap_height = 96; +/* Width & height in pixels of each cell as displayed on screen. */ +unsigned int magnifier = 2; - void main() - { - unsigned int init_length, x, y, seed; - unsigned long generation = 0; - char gen_text[80]; - long bios_time, start_bios_time; +void main() +{ + unsigned int init_length, x, y, seed; + unsigned long generation = 0; + char gen_text[80]; + long bios_time, start_bios_time; - cellmap current_map(cellmap_height, cellmap_width); - cellmap next_map(cellmap_height, cellmap_width); + cellmap current_map(cellmap_height, cellmap_width); + cellmap next_map(cellmap_height, cellmap_width); - // Get the seed; seed randomly if 0 entered - cout << "Seed (0 for random seed): "; - cin >> seed; - if (seed == 0) seed = (unsigned) time(NULL); + // Get the seed; seed randomly if 0 entered + cout << "Seed (0 for random seed): "; + cin >> seed; + if (seed == 0) seed = (unsigned) time(NULL); - // Randomly initialize the initial cell map - cout << "Initializing..."; - srand(seed); - init_length = (cellmap_height * cellmap_width) / 2; - do { - x = random(cellmap_width); - y = random(cellmap_height); - next_map.set_cell(x, y); - } while (—init_length); - current_map.copy_cells(next_map); // put init map in current_map + // Randomly initialize the initial cell map + cout << "Initializing..."; + srand(seed); + init_length = (cellmap_height * cellmap_width) / 2; + do { + x = random(cellmap_width); + y = random(cellmap_height); + next_map.set_cell(x, y); + } while (—init_length); + current_map.copy_cells(next_map); // put init map in current_map - enter_display_mode(); + enter_display_mode(); - // Keep recalculating and redisplaying generations until a key - // is pressed - show_text(0, MSG_LINE, "Generation: "); - start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time); - do { - 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); - // Make current_map current again - current_map.copy_cells(next_map); - #if LIMIT_18_HZ - // Limit to a maximum of 18.2 frames per second,for visibility - do { - _bios_timeofday(_TIME_GETCLOCK, &bios_time); - } while (start_bios_time == bios_time); - start_bios_time = bios_time; - #endif - } while (!kbhit()); - getch(); // clear keypress - exit_display_mode(); - cout << "Total generations: " << generation << "\nSeed: " << - seed << "\n"; - } + // Keep recalculating and redisplaying generations until a key + // is pressed + show_text(0, MSG_LINE, "Generation: "); + start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time); + do { + 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); + // Make current_map current again + current_map.copy_cells(next_map); +#if LIMIT_18_HZ + // Limit to a maximum of 18.2 frames per second,for visibility + do { + _bios_timeofday(_TIME_GETCLOCK, &bios_time); + } while (start_bios_time == bios_time); + start_bios_time = bios_time; +#endif + } while (!kbhit()); + getch(); // clear keypress + exit_display_mode(); + cout << "Total generations: " << generation << "\nSeed: " << + seed << "\n"; +} - /* cellmap constructor. */ - cellmap::cellmap(unsigned int h, unsigned int w) - { - width = w; - width_in_bytes = (w + 7) / 8; - height = h; - length_in_bytes = width_in_bytes * h; - cells = new unsigned char[length_in_bytes]; // cell storage - memset(cells, 0, length_in_bytes); // clear all cells, to start - } +/* cellmap constructor. */ +cellmap::cellmap(unsigned int h, unsigned int w) +{ + width = w; + width_in_bytes = (w + 7) / 8; + height = h; + length_in_bytes = width_in_bytes * h; + cells = new unsigned char[length_in_bytes]; // cell storage + memset(cells, 0, length_in_bytes); // clear all cells, to start +} - /* cellmap destructor. */ - cellmap::~cellmap(void) - { - delete[] cells; - } +/* cellmap destructor. */ +cellmap::~cellmap(void) +{ + delete[] cells; +} - /* Copies one cellmap's cells to another cellmap. Both cellmaps are - assumed to be the same size. */ - void cellmap::copy_cells(cellmap &sourcemap) - { - memcpy(cells, sourcemap.cells, length_in_bytes); - } +/* Copies one cellmap's cells to another cellmap. Both cellmaps are + assumed to be the same size. */ +void cellmap::copy_cells(cellmap &sourcemap) +{ + memcpy(cells, sourcemap.cells, length_in_bytes); +} - /* Turns cell on. */ - void cellmap::set_cell(unsigned int x, unsigned int y) - { - unsigned char *cell_ptr = - cells + (y * width_in_bytes) + (x / 8); +/* Turns cell on. */ +void cellmap::set_cell(unsigned int x, unsigned int y) +{ + unsigned char *cell_ptr = + cells + (y * width_in_bytes) + (x / 8); - *(cell_ptr) |= 0x80 >> (x & 0x07); - } + *(cell_ptr) |= 0x80 >> (x & 0x07); +} - /* Turns cell off. */ - void cellmap::clear_cell(unsigned int x, unsigned int y) - { - unsigned char *cell_ptr = - cells + (y * width_in_bytes) + (x / 8); +/* Turns cell off. */ +void cellmap::clear_cell(unsigned int x, unsigned int y) +{ + unsigned char *cell_ptr = + cells + (y * width_in_bytes) + (x / 8); - *(cell_ptr) &= ~(0x80 >> (x & 0x07)); - } + *(cell_ptr) &= ~(0x80 >> (x & 0x07)); +} - /* Returns cell state (1=on or 0=off), optionally wrapping at the - borders around to the opposite edge. */ - int cellmap::cell_state(int x, int y) - { - unsigned char *cell_ptr; +/* Returns cell state (1=on or 0=off), optionally wrapping at the + borders around to the opposite edge. */ +int cellmap::cell_state(int x, int y) +{ + unsigned char *cell_ptr; - #if WRAP_EDGES - while (x < 0) x += width; // wrap, if necessary - while (x >= width) x -= width; - while (y < 0) y += height; - while (y >= height) y -= height; - #else - if ((x < 0) || (x >= width) || (y < 0) || (y >= height)) - return 0; // return 0 for off edges if no wrapping - #endif - cell_ptr = cells + (y * width_in_bytes) + (x / 8); - return (*cell_ptr & (0x80 >> (x & 0x07))) ? 1 : 0; - } +#if WRAP_EDGES + while (x < 0) x += width; // wrap, if necessary + while (x >= width) x -= width; + while (y < 0) y += height; + while (y >= height) y -= height; +#else + if ((x < 0) || (x >= width) || (y < 0) || (y >= height)) + return 0; // return 0 for off edges if no wrapping +#endif + cell_ptr = cells + (y * width_in_bytes) + (x / 8); + return (*cell_ptr & (0x80 >> (x & 0x07))) ? 1 : 0; +} - /* Calculates the next generation of a cellmap and stores it in - next_map. */ - void cellmap::next_generation(cellmap& next_map) - { - unsigned int x, y, neighbor_count; +/* Calculates the next generation of a cellmap and stores it in + next_map. */ +void cellmap::next_generation(cellmap& next_map) +{ + unsigned int x, y, neighbor_count; - for (y=0; y - #include - #include +```cpp +/* VGA mode 13h functions for Game of Life. + Tested with Borland C++. */ +#include +#include +#include - #define TEXT_X_OFFSET 27 - #define SCREEN_WIDTH_IN_BYTES 320 +#define TEXT_X_OFFSET 27 +#define SCREEN_WIDTH_IN_BYTES 320 - /* Width & height in pixels of each cell. */ - extern unsigned int magnifier; +/* Width & height in pixels of each cell. */ +extern unsigned int magnifier; - /* Mode 13h draw pixel function. Pixels are of width & height - specified by magnifier. */ - void draw_pixel(unsigned int x, unsigned int y, unsigned int color) - { - #define SCREEN_SEGMENT 0xA000 - unsigned char far *screen_ptr; - int i, j; +/* Mode 13h draw pixel function. Pixels are of width & height + specified by magnifier. */ +void draw_pixel(unsigned int x, unsigned int y, unsigned int color) +{ +#define SCREEN_SEGMENT 0xA000 + unsigned char far *screen_ptr; + int i, j; - FP_SEG(screen_ptr) = SCREEN_SEGMENT; - FP_OFF(screen_ptr) = - y * magnifier * SCREEN_WIDTH_IN_BYTES + x * magnifier; - for (i=0; i> (x & 0x07); - } + *(cell_ptr) |= 0x80 >> (x & 0x07); +} - /* Turns cell off. x and y are offset by 1 byte down and to the right, - to compensate for the padding bytes around the cell map. */ - void cellmap::clear_cell(unsigned int x, unsigned int y) - { - unsigned char *cell_ptr = - cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1); +/* Turns cell off. x and y are offset by 1 byte down and to the right, +to compensate for the padding bytes around the cell map. */ +void cellmap::clear_cell(unsigned int x, unsigned int y) +{ + unsigned char *cell_ptr = + cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1); - *(cell_ptr) &= ~(0x80 >> (x & 0x07)); - } + *(cell_ptr) &= ~(0x80 >> (x & 0x07)); +} - /* Returns cell state (1=on or 0=off). x and y are offset by 1 byte - down and to the right, to - compensate for the padding bytes around - the cell map. */ - int cellmap::cell_state(int x, int y) - { - unsigned char *cell_ptr = - cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1); +/* Returns cell state (1=on or 0=off). x and y are offset by 1 byte + down and to the right, to +compensate for the padding bytes around + the cell map. */ +int cellmap::cell_state(int x, int y) +{ + unsigned char *cell_ptr = + cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1); - return (*cell_ptr & (0x80 >> (x & 0x07))) ? 1 : 0; - } + return (*cell_ptr & (0x80 >> (x & 0x07))) ? 1 : 0; +} - /* Counts the number of neighboring on-cells for specified cell. */ - int cellmap::count_neighbors(int x, int y) - { - unsigned char *cell_ptr, mask; - unsigned int neighbor_count; +/* Counts the number of neighboring on-cells for specified cell. */ +int cellmap::count_neighbors(int x, int y) +{ + unsigned char *cell_ptr, mask; + unsigned int neighbor_count; - // Point to upper left neighbor - cell_ptr = cells + ((y * width_in_bytes) + ((x + 7) / 8)); - mask = 0x80 >> ((x - 1) & 0x07); - // Count upper left neighbor - neighbor_count = (*cell_ptr & mask) ? 1 : 0; - // Count left neighbor - if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++; - // Count lower left neighbor - if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++; + // Point to upper left neighbor + cell_ptr = cells + ((y * width_in_bytes) + ((x + 7) / 8)); + mask = 0x80 >> ((x - 1) & 0x07); + // Count upper left neighbor + neighbor_count = (*cell_ptr & mask) ? 1 : 0; + // Count left neighbor + if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++; + // Count lower left neighbor + if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++; - // Point to upper neighbor - if ((mask >>= 1) == 0) { - mask = 0x80; - cell_ptr++; - } - // Count upper neighbor - if ((*cell_ptr & mask)) neighbor_count++; - // Count lower neighbor - if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++; + // Point to upper neighbor + if ((mask >>= 1) == 0) { + mask = 0x80; + cell_ptr++; + } + // Count upper neighbor + if ((*cell_ptr & mask)) neighbor_count++; + // Count lower neighbor + if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++; - // Point to upper right neighbor - if ((mask >>= 1) == 0) { - mask = 0x80; - cell_ptr++; - } - // Count upper right neighbor - if ((*cell_ptr & mask)) neighbor_count++; - // Count right neighbor - if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++; - // Count lower right neighbor - if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++; + // Point to upper right neighbor + if ((mask >>= 1) == 0) { + mask = 0x80; + cell_ptr++; + } + // Count upper right neighbor + if ((*cell_ptr & mask)) neighbor_count++; + // Count right neighbor + if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++; + // Count lower right neighbor + if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++; - return neighbor_count; - } + return neighbor_count; +} - /* Calculates the next generation of current_map and stores it in - next_map. */ - void cellmap::next_generation(cellmap& next_map) - { - unsigned int x, y, neighbor_count; +/* Calculates the next generation of current_map and stores it in + next_map. */ +void cellmap::next_generation(cellmap& next_map) +{ + unsigned int x, y, neighbor_count; - for (y=0; y>= 1) == 0) { - mask = 0x80; - cell_ptr++; - } - // Remember where to find the current cell - current_cell_ptr = cell_ptr + width_in_bytes; - current_mask = mask; - // Count upper neighbor - if ((*cell_ptr & mask)) neighbor_count++; - // Count lower neighbor - if ((*(cell_ptr + width_in_bytesX2) & mask)) - neighbor_count++; - // Point to upper right neighbor - if ((mask >>= 1) == 0) { - mask = 0x80; - cell_ptr++; - } - // Count upper right neighbor - if ((*cell_ptr & mask)) neighbor_count++; - // Count right neighbor - if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++; - // Count lower right neighbor - if ((*(cell_ptr + width_in_bytesX2) & mask)) - neighbor_count++; - if (*current_cell_ptr & current_mask) { - if ((neighbor_count != 2) && (neighbor_count != 3)) { - *(dest_cell_ptr + (current_cell_ptr - cells)) &= - ~current_mask; // turn off cell - draw_pixel(x, y, OFF_COLOR); - } - } else { - if (neighbor_count == 3) { - *(dest_cell_ptr + (current_cell_ptr - cells)) |= - current_mask; // turn on cell - draw_pixel(x, y, ON_COLOR); - } - } - // Advance to the next cell on row - if ((base_mask >>= 1) == 0) { - base_mask = 0x80; - base_cell_ptr++; // advance to the next cell byte - } - } - row_cell_ptr += width_in_bytes; // point to start of next row - } - } + // Process all cells in the current cellmap + row_cell_ptr = cells; // point to upper left neighbor of + // first cell in cell map + for (y=0; y>= 1) == 0) { + mask = 0x80; + cell_ptr++; + } + // Remember where to find the current cell + current_cell_ptr = cell_ptr + width_in_bytes; + current_mask = mask; + // Count upper neighbor + if ((*cell_ptr & mask)) neighbor_count++; + // Count lower neighbor + if ((*(cell_ptr + width_in_bytesX2) & mask)) + neighbor_count++; + // Point to upper right neighbor + if ((mask >>= 1) == 0) { + mask = 0x80; + cell_ptr++; + } + // Count upper right neighbor + if ((*cell_ptr & mask)) neighbor_count++; + // Count right neighbor + if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++; + // Count lower right neighbor + if ((*(cell_ptr + width_in_bytesX2) & mask)) + neighbor_count++; + if (*current_cell_ptr & current_mask) { + if ((neighbor_count != 2) && (neighbor_count != 3)) { + *(dest_cell_ptr + (current_cell_ptr - cells)) &= + ~current_mask; // turn off cell + draw_pixel(x, y, OFF_COLOR); + } + } else { + if (neighbor_count == 3) { + *(dest_cell_ptr + (current_cell_ptr - cells)) |= + current_mask; // turn on cell + draw_pixel(x, y, ON_COLOR); + } + } + // Advance to the next cell on row + if ((base_mask >>= 1) == 0) { + base_mask = 0x80; + base_cell_ptr++; // advance to the next cell byte + } + } + row_cell_ptr += width_in_bytes; // point to start of next row + } +} +``` Listing 17.4 and Listing 17.3 are functionally the same; the only difference lies in how **next\_generation()** is implemented. (Only diff --git a/17-07.md b/17-07.md index 2b06bb1..ea366f5 100644 --- a/17-07.md +++ b/17-07.md @@ -12,276 +12,278 @@ pages: 340-345 **LISTING 17.5 L17-5.CPP** - /* C++ Game of Life implementation for any mode for which mode set - and draw pixel functions can be provided. The cellmap stores the - neighbor count for each cell as well as the state of each cell; - this allows very fast next-state determination. Edges always wrap - in this implementation. - Tested with Borland C++. To run, link with Listing 17.2 - in the large model. */ - #include - #include - #include - #include - #include - #include - #include - #include +```cpp +/* C++ Game of Life implementation for any mode for which mode set + and draw pixel functions can be provided. The cellmap stores the + neighbor count for each cell as well as the state of each cell; + this allows very fast next-state determination. Edges always wrap + in this implementation. + Tested with Borland C++. To run, link with Listing 17.2 + in the large model. */ +#include +#include +#include +#include +#include +#include +#include +#include - #define ON_COLOR 15 // on-cell pixel color - #define OFF_COLOR 0 // off-cell pixel color - #define MSG_LINE 10 // row for text messages - #define GENERATION_LINE 12 // row for generation # display - #define LIMIT_18_HZ 0 // set 1 to to maximum frame rate = 18Hz +#define ON_COLOR 15 // on-cell pixel color +#define OFF_COLOR 0 // off-cell pixel color +#define MSG_LINE 10 // row for text messages +#define GENERATION_LINE 12 // row for generation # display +#define LIMIT_18_HZ 0 // set 1 to to maximum frame rate = 18Hz - class cellmap { - private: - unsigned char *cells; - unsigned char *temp_cells; - unsigned int width; - unsigned int height; - unsigned int length_in_bytes; - public: - cellmap(unsigned int h, unsigned int v); - ~cellmap(void); - void set_cell(unsigned int x, unsigned int y); - void clear_cell(unsigned int x, unsigned int y); - int cell_state(int x, int y); - int count_neighbors(int x, int y); - void next_generation(void); - void init(void); - }; +class cellmap { +private: + unsigned char *cells; + unsigned char *temp_cells; + unsigned int width; + unsigned int height; + unsigned int length_in_bytes; +public: + cellmap(unsigned int h, unsigned int v); + ~cellmap(void); + void set_cell(unsigned int x, unsigned int y); + void clear_cell(unsigned int x, unsigned int y); + int cell_state(int x, int y); + int count_neighbors(int x, int y); + void next_generation(void); + void init(void); +}; - extern void enter_display_mode(void); - extern void exit_display_mode(void); - extern void draw_pixel(unsigned int X, unsigned int Y, - unsigned int Color); - extern void show_text(int x, int y, char *text); +extern void enter_display_mode(void); +extern void exit_display_mode(void); +extern void draw_pixel(unsigned int X, unsigned int Y, + unsigned int Color); +extern void show_text(int x, int y, char *text); - /* Controls the size of the cell map. Must be within the capabilities - of the display mode, and must be limited to leave room for text - display at right. */ - unsigned int cellmap_width = 96; - unsigned int cellmap_height = 96; +/* Controls the size of the cell map. Must be within the capabilities + of the display mode, and must be limited to leave room for text + display at right. */ +unsigned int cellmap_width = 96; +unsigned int cellmap_height = 96; - /* Width & height in pixels of each cell. */ - unsigned int magnifier = 2; +/* Width & height in pixels of each cell. */ +unsigned int magnifier = 2; - /* Randomizing seed */ - unsigned int seed; +/* Randomizing seed */ +unsigned int seed; - void main() - { - unsigned long generation = 0; - char gen_text[80]; - long bios_time, start_bios_time; +void main() +{ + unsigned long generation = 0; + char gen_text[80]; + long bios_time, start_bios_time; - cellmap current_map(cellmap_height, cellmap_width); + cellmap current_map(cellmap_height, cellmap_width); - current_map.init(); // randomly initialize cell map + current_map.init(); // randomly initialize cell map - enter_display_mode(); + enter_display_mode(); - // Keep recalculating and redisplaying generations until any key - // is pressed - show_text(0, MSG_LINE, "Generation: "); - start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time); - do { - generation++; - sprintf(gen_text, "%10lu", generation); - show_text(1, GENERATION_LINE, gen_text); - // Recalculate and draw the next generation - current_map.next_generation(); - #if LIMIT_18_HZ - // Limit to a maximum of 18.2 frames per second, for visibility - do { - _bios_timeofday(_TIME_GETCLOCK, &bios_time); - } while (start_bios_time == bios_time); - start_bios_time = bios_time; - #endif - } while (!kbhit()); + // Keep recalculating and redisplaying generations until any key + // is pressed + show_text(0, MSG_LINE, "Generation: "); + start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time); + do { + generation++; + sprintf(gen_text, "%10lu", generation); + show_text(1, GENERATION_LINE, gen_text); + // Recalculate and draw the next generation + current_map.next_generation(); +#if LIMIT_18_HZ + // Limit to a maximum of 18.2 frames per second, for visibility + do { + _bios_timeofday(_TIME_GETCLOCK, &bios_time); + } while (start_bios_time == bios_time); + start_bios_time = bios_time; +#endif + } while (!kbhit()); - getch(); // clear keypress - exit_display_mode(); - cout << "Total generations: " << generation << "\nSeed: " << - seed << "\n"; - } + getch(); // clear keypress + exit_display_mode(); + cout << "Total generations: " << generation << "\nSeed: " << + seed << "\n"; +} - /* cellmap constructor. */ - cellmap::cellmap(unsigned int h, unsigned int w) - { - width = w; - height = h; - length_in_bytes = w * h; - 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"); - exit(1); - } - memset(cells, 0, length_in_bytes); // clear all cells, to start - } +/* cellmap constructor. */ +cellmap::cellmap(unsigned int h, unsigned int w) +{ + width = w; + height = h; + length_in_bytes = w * h; + 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"); + exit(1); + } + memset(cells, 0, length_in_bytes); // clear all cells, to start +} - /* cellmap destructor. */ - cellmap::~cellmap(void) - { - delete[] cells; - delete[] temp_cells; - } +/* cellmap destructor. */ +cellmap::~cellmap(void) +{ + delete[] cells; + delete[] temp_cells; +} - /* Turns an off-cell on, incrementing the on-neighbor count for the - eight neighboring cells. */ - void cellmap::set_cell(unsigned int x, unsigned int y) - { - unsigned int w = width, h = height; - int xoleft, xoright, yoabove, yobelow; - unsigned char *cell_ptr = cells + (y * w) + x; +/* Turns an off-cell on, incrementing the on-neighbor count for the + eight neighboring cells. */ +void cellmap::set_cell(unsigned int x, unsigned int y) +{ + unsigned int w = width, h = height; + int xoleft, xoright, yoabove, yobelow; + unsigned char *cell_ptr = cells + (y * w) + x; - // Calculate the offsets to the eight neighboring cells, - // accounting for wrapping around at the edges of the cell map - if (x == 0) - xoleft = w - 1; - else - xoleft = -1; - if (y == 0) - yoabove = length_in_bytes - w; - else - yoabove = -w; - if (x == (w - 1)) - xoright = -(w - 1); - else - xoright = 1; - if (y == (h - 1)) - yobelow = -(length_in_bytes - w); - else - yobelow = w; + // Calculate the offsets to the eight neighboring cells, + // accounting for wrapping around at the edges of the cell map + if (x == 0) + xoleft = w - 1; + else + xoleft = -1; + if (y == 0) + yoabove = length_in_bytes - w; + else + yoabove = -w; + if (x == (w - 1)) + xoright = -(w - 1); + else + xoright = 1; + if (y == (h - 1)) + yobelow = -(length_in_bytes - w); + else + yobelow = w; - *(cell_ptr) |= 0x01; - *(cell_ptr + yoabove + xoleft) += 2; - *(cell_ptr + yoabove) += 2; - *(cell_ptr + yoabove + xoright) += 2; - *(cell_ptr + xoleft) += 2; - *(cell_ptr + xoright) += 2; - *(cell_ptr + yobelow + xoleft) += 2; - *(cell_ptr + yobelow) += 2; - *(cell_ptr + yobelow + xoright) += 2; - } + *(cell_ptr) |= 0x01; + *(cell_ptr + yoabove + xoleft) += 2; + *(cell_ptr + yoabove) += 2; + *(cell_ptr + yoabove + xoright) += 2; + *(cell_ptr + xoleft) += 2; + *(cell_ptr + xoright) += 2; + *(cell_ptr + yobelow + xoleft) += 2; + *(cell_ptr + yobelow) += 2; + *(cell_ptr + yobelow + xoright) += 2; +} - /* Turns an on-cell off, decrementing the on-neighbor count for the - eight neighboring cells. */ - void cellmap::clear_cell(unsigned int x, unsigned int y) - { - unsigned int w = width, h = height; - int xoleft, xoright, yoabove, yobelow; - unsigned char *cell_ptr = cells + (y * w) + x; +/* Turns an on-cell off, decrementing the on-neighbor count for the + eight neighboring cells. */ +void cellmap::clear_cell(unsigned int x, unsigned int y) +{ + unsigned int w = width, h = height; + int xoleft, xoright, yoabove, yobelow; + unsigned char *cell_ptr = cells + (y * w) + x; - // Calculate the offsets to the eight neighboring cells, - // accounting for wrapping around at the edges of the cell map - if (x == 0) - xoleft = w - 1; - else - xoleft = -1; - if (y == 0) - yoabove = length_in_bytes - w; - else - yoabove = -w; - if (x == (w - 1)) - xoright = -(w - 1); - else - xoright = 1; - if (y == (h - 1)) - yobelow = -(length_in_bytes - w); - else - yobelow = w; + // Calculate the offsets to the eight neighboring cells, + // accounting for wrapping around at the edges of the cell map + if (x == 0) + xoleft = w - 1; + else + xoleft = -1; + if (y == 0) + yoabove = length_in_bytes - w; + else + yoabove = -w; + if (x == (w - 1)) + xoright = -(w - 1); + else + xoright = 1; + if (y == (h - 1)) + yobelow = -(length_in_bytes - w); + else + yobelow = w; - *(cell_ptr) &= ~0x01; - *(cell_ptr + yoabove + xoleft) -= 2; - *(cell_ptr + yoabove ) -= 2; - *(cell_ptr + yoabove + xoright) -= 2; - *(cell_ptr + xoleft) -= 2; - *(cell_ptr + xoright) -= 2; - *(cell_ptr + yobelow + xoleft) -= 2; - *(cell_ptr + yobelow) -= 2; - *(cell_ptr + yobelow + xoright) -= 2; - } + *(cell_ptr) &= ~0x01; + *(cell_ptr + yoabove + xoleft) -= 2; + *(cell_ptr + yoabove ) -= 2; + *(cell_ptr + yoabove + xoright) -= 2; + *(cell_ptr + xoleft) -= 2; + *(cell_ptr + xoright) -= 2; + *(cell_ptr + yobelow + xoleft) -= 2; + *(cell_ptr + yobelow) -= 2; + *(cell_ptr + yobelow + xoright) -= 2; +} - /* Returns cell state (1=on or 0=off). */ - int cellmap::cell_state(int x, int y) - { - unsigned char *cell_ptr; +/* Returns cell state (1=on or 0=off). */ +int cellmap::cell_state(int x, int y) +{ + unsigned char *cell_ptr; - cell_ptr = cells + (y * width) + x; - return *cell_ptr & 0x01; - } + cell_ptr = cells + (y * width) + x; + return *cell_ptr & 0x01; +} - /* Calculates and displays the next generation of current_map */ - void cellmap::next_generation() - { - unsigned int x, y, count; - unsigned int h = height, w = width; - unsigned char *cell_ptr, *row_cell_ptr; +/* Calculates and displays the next generation of current_map */ +void cellmap::next_generation() +{ + unsigned int x, y, count; + unsigned int h = height, w = width; + unsigned char *cell_ptr, *row_cell_ptr; - // Copy to temp map, so we can have an unaltered version from - // which to work - memcpy(temp_cells, cells, length_in_bytes); + // Copy to temp map, so we can have an unaltered version from + // which to work + memcpy(temp_cells, cells, length_in_bytes); - // Process all cells in the current cell map - cell_ptr = temp_cells; // first cell in cell map - for (y=0; y= w) goto RowDone; - } - // 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 - // 2 or 3 neighbors - if ((count != 2) && (count != 3)) { - clear_cell(x, y); - draw_pixel(x, y, OFF_COLOR); - } - } else { - // Cell is off; turn it on if it has exactly 3 neighbors - if (count == 3) { - set_cell(x, y); - draw_pixel(x, y, ON_COLOR); - } - } - // Advance to the next cell - cell_ptr++; // advance to the next cell byte - } while (++x < w); - RowDone: - } - } + // Process all cells in the current cell map + cell_ptr = temp_cells; // first cell in cell map + for (y=0; y= w) goto RowDone; + } + // 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 + // 2 or 3 neighbors + if ((count != 2) && (count != 3)) { + clear_cell(x, y); + draw_pixel(x, y, OFF_COLOR); + } + } else { + // Cell is off; turn it on if it has exactly 3 neighbors + if (count == 3) { + set_cell(x, y); + draw_pixel(x, y, ON_COLOR); + } + } + // Advance to the next cell + cell_ptr++; // advance to the next cell byte + } while (++x < w); +RowDone: + } +} - /* Randomly initializes the cellmap to about 50% on-pixels. */ - void cellmap::init() - { - unsigned int x, y, init_length; +/* Randomly initializes the cellmap to about 50% on-pixels. */ +void cellmap::init() +{ + unsigned int x, y, init_length; - // Get the seed; seed randomly if 0 entered - cout << "Seed (0 for random seed): "; - cin >> seed; - if (seed == 0) seed = (unsigned) time(NULL); + // Get the seed; seed randomly if 0 entered + 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..."; - srand(seed); - init_length = (height * width) / 2; - do { - x = random(width); - y = random(height); - if (cell_state(x, y) == 0) { - set_cell(x, y); - } - } while (—init_length); - } + // 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..."; + srand(seed); + init_length = (height * width) / 2; + do { + x = random(width); + y = random(height); + if (cell_state(x, y) == 0) { + set_cell(x, y); + } + } while (—init_length); +} +``` diff --git a/18-03.md b/18-03.md index 01e0e10..70bd95f 100644 --- a/18-03.md +++ b/18-03.md @@ -12,503 +12,507 @@ pages: 352-361 **LISTING 18.1 BUILD.BAT** - bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 lcomp.c - lcomp > qlife.asm - tasmx /mx /kh30000 qlife - bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 qlife.obj main.c video.c +```bat +bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 lcomp.c +lcomp > qlife.asm +tasmx /mx /kh30000 qlife +bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 qlife.obj main.c video.c +``` **LISTING 18.2 LCOMP.C** - // LCOMP.C - // - // Life compiler, ver 1.3 - // - // David Stafford - // +```c +// LCOMP.C +// +// Life compiler, ver 1.3 +// +// David Stafford +// - #include - #include - #include "life.h" +#include +#include +#include "life.h" - #define LIST_LIMIT (46 * 138) // when we need to use es: +#define LIST_LIMIT (46 * 138) // when we need to use es: - int Old, New, Edge, Label; - char Buf[ 20 ]; +int Old, New, Edge, Label; +char Buf[ 20 ]; - void Next1( void ) +void Next1( void ) + { + char *Seg = ""; + + 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" ); + } + +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" ); + } + +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" ); + + for( j = 0; j < HEIGHT; j++ ) + { + for( i = 0; i < WIDTH; i++ ) { - char *Seg = ""; - - 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" ); - } - - 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" ); - } - - 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" ); - - for( j = 0; j < HEIGHT; j++ ) + if( i == 0 || i == WIDTH-1 || j == 0 || j == HEIGHT-1 ) { - for( i = 0; i < WIDTH; i++ ) - { - if( i == 0 || i == WIDTH-1 || j == 0 || j == HEIGHT-1 ) - { - printf( "dw 8000h\n" ); - } - else - { - printf( "dw 0\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 ); - } - } - - 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( "dw 8000h\n" ); } else { - printf( "Change1 dw offset DGROUP:_ChangeList1\n" ); - printf( "Change2 dw offset DGROUP:_ChangeList2\n" ); + printf( "dw 0\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( "_LDMAP segment para public ‘FAR_DATA'\n" ); - - do - { - // Current cell states - C1 = (x & 0x0800) >> 11; - C2 = (x & 0x0400) >> 10; - C3 = (x & 0x0200) >> 9; - - // Neighbor counts - N1 = (x & 0x01C0) >> 6; - N2 = (x & 0x0038) >> 3; - N3 = (x & 0x0007); - - y = x & 0x8FFF; // Preserve all but the next generation states - - if( C1 && ((N1 + C2 == 2) || (N1 + C2 == 3)) ) - { - y |= 0x4000; - } - - if( !C1 && (N1 + C2 == 3) ) - { - y |= 0x4000; - } - - if( C2 && ((N2 + C1 + C3 == 2) || (N2 + C1 + C3 == 3)) ) - { - y |= 0x2000; - } - - if( !C2 && (N2 + C1 + C3 == 3) ) - { - y |= 0x2000; - } - - if( C3 && ((N3 + C2 == 2) || (N3 + C2 == 3)) ) - { - y |= 0x1000; - } - - if( !C3 && (N3 + C2 == 3) ) - { - y |= 0x1000; - } - - printf( "db 0%02xh\n", y >> 8 ); - } - while( ++x != 0 ); - - printf( "ends\n\n" ); } + } - void GetUpAndDown( void ) + printf( "ChangeCell dw 0\n" ); + printf( "_RowColMap label word\n" ); + + for( j = 0; j < HEIGHT; j++ ) + { + for( i = 0; i < WIDTH; i++ ) { - 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( "dw 0%02x%02xh\n", j, i * 3 ); } + } - void FirstPass( void ) + 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" ); + } + else + { + 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( "_LDMAP segment para public ‘FAR_DATA'\n" ); + + do + { + // Current cell states + C1 = (x & 0x0800) >> 11; + C2 = (x & 0x0400) >> 10; + C3 = (x & 0x0200) >> 9; + + // Neighbor counts + N1 = (x & 0x01C0) >> 6; + N2 = (x & 0x0038) >> 3; + N3 = (x & 0x0007); + + y = x & 0x8FFF; // Preserve all but the next generation states + + if( C1 && ((N1 + C2 == 2) || (N1 + C2 == 3)) ) { - char *Op; - unsigned short UpDown = 0; - - 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 ); - - // 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 - - if( ((New ^ Old) & 6) == 6 ) - { - 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" ); - } - } - else - { - if( ((New ^ Old) & 3) == 3 ) - { - printf( "mov word ptr fs:[bx+1],0%02x%02xh\n", - (New & 1) ? 15 : 0, - (New & 2) ? 15 : 0 ); - } - else - { - if( (New ^ Old) & 2 ) - { - 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" ); - } - } - - if( (New ^ Old) & 4 ) - { - printf( "mov byte ptr fs:[bx],%s\n", - (New & 4) ? "15" : "dl" ); - } - } - #endif - - if( (New ^ Old) & 4 ) UpDown += (New & 4) ? 0x48 : -0x48; - if( (New ^ Old) & 2 ) UpDown += (New & 2) ? 0x49 : -0x49; - if( (New ^ Old) & 1 ) UpDown += (New & 1) ? 0x09 : -0x09; - - if( Edge ) - { - GetUpAndDown(); // ah = row, al = col, cx = up, dx = down - - 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 ); - - 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 ); - } - - 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 ); - - 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( "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" ); - } - else - { - if( (New ^ Old) & 4 ) - { - 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 ); - } - - if( (New ^ Old) & 1 ) - { - 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 ); - } - - if( abs( UpDown ) > 1 ) - { - 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"; - - printf( "%s byte ptr [bp+%d]\n", Op, UP ); - printf( "%s byte ptr [bp+%d]\n", Op, DOWN ); - } - } - - Next1(); + y |= 0x4000; } - void Test( char *Offset, char *Str ) + if( !C1 && (N1 + C2 == 3) ) { - 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 ); + y |= 0x4000; } - void Fix( char *Offset, char *Str, int JumpBack ) + if( C2 && ((N2 + C1 + C3 == 2) || (N2 + C1 + C3 == 3)) ) { - 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" ); - - printf( "stosw\n" ); - - if( JumpBack ) printf( "jmp short %s%d\n", Str, Label ); + y |= 0x2000; } - void SecondPass( void ) + if( !C2 && (N2 + C1 + C3 == 3) ) { - printf( "org 0%02x00h\n", - (Edge << 7) + (New << 4) + (Old << 1) + 1 ); - - if( Edge ) - { - // 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" ); - } - - 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 ); - - 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( "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 ); - - 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" ); - } - - if( New == Old ) Test( "0", "CENTER" ); - - 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 ); - } - 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 ); - } - - if( New == Old ) Fix( "0", "CENTER", 0 ); - - if( Edge ) printf( "pop si\n" "mov dl,0\n" ); - - Next2(); + y |= 0x2000; } - void main( void ) + if( C3 && ((N3 + C2 == 2) || (N3 + C2 == 3)) ) { - char *Seg = "ds"; + y |= 0x1000; + } - BuildMaps(); + if( !C3 && (N3 + C2 == 3) ) + { + y |= 0x1000; + } - 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( "db 0%02xh\n", y >> 8 ); + } + while( ++x != 0 ); - for( Edge = 0; Edge <= 1; Edge++ ) + 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 ); + } + +void FirstPass( void ) + { + char *Op; + unsigned short UpDown = 0; + + 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 ); + + // 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 + + if( ((New ^ Old) & 6) == 6 ) + { + 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" ); + } + } + else + { + if( ((New ^ Old) & 3) == 3 ) + { + printf( "mov word ptr fs:[bx+1],0%02x%02xh\n", + (New & 1) ? 15 : 0, + (New & 2) ? 15 : 0 ); + } + else + { + if( (New ^ Old) & 2 ) { - for( New = 0; New < 8; New++ ) - { - for( Old = 0; Old < 8; Old++ ) - { - if( New != Old ) FirstPass(); Label++; - SecondPass(); Label++; - } - } + printf( "mov byte ptr fs:[bx+1],%s\n", + (New & 2) ? "15" : "dl" ); } - // 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" ); - Next2(); - - // entry point - printf( "_NextGen: push ds bp si di\n" "cld\n" ); - - if( WIDTH * HEIGHT > LIST_LIMIT ) Seg = "seg _CHANGE"; - - printf( "mov ax,%s\n", Seg ); - printf( "mov es,ax\n" ); - - #ifndef NODRAW - printf( "mov ax,0A000h\n" ); - printf( "mov fs,ax\n" ); - #endif - - printf( "mov si,Change1\n" ); - printf( "mov dl,0\n" ); - Next1(); - - printf( "LIFE ends\nend\n" ); + if( (New ^ Old) & 1 ) + { + 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" ); + } + } + #endif + + if( (New ^ Old) & 4 ) UpDown += (New & 4) ? 0x48 : -0x48; + if( (New ^ Old) & 2 ) UpDown += (New & 2) ? 0x49 : -0x49; + if( (New ^ Old) & 1 ) UpDown += (New & 1) ? 0x09 : -0x09; + + if( Edge ) + { + GetUpAndDown(); // ah = row, al = col, cx = up, dx = down + + 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 ); + + 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 ); + } + + 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 ); + + 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( "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" ); + } + else + { + if( (New ^ Old) & 4 ) + { + 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 ); + } + + if( (New ^ Old) & 1 ) + { + 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 ); + } + + if( abs( UpDown ) > 1 ) + { + 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"; + + printf( "%s byte ptr [bp+%d]\n", Op, UP ); + printf( "%s byte ptr [bp+%d]\n", Op, DOWN ); + } + } + + Next1(); + } + +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 ); + } + +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 ); + + if( *Offset != ‘0' ) printf( "lea ax,[bp+%s]\n", Offset ); + else printf( "mov ax,bp\n" ); + + printf( "stosw\n" ); + + if( JumpBack ) printf( "jmp short %s%d\n", Str, Label ); + } + +void SecondPass( void ) + { + printf( "org 0%02x00h\n", + (Edge << 7) + (New << 4) + (Old << 1) + 1 ); + + if( Edge ) + { + // 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" ); + } + + 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 ); + + 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( "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 ); + + 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" ); + } + + if( New == Old ) Test( "0", "CENTER" ); + + 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 ); + } + 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 ); + } + + if( New == Old ) Fix( "0", "CENTER", 0 ); + + if( Edge ) printf( "pop si\n" "mov dl,0\n" ); + + Next2(); + } + +void main( void ) + { + 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" ); + + for( Edge = 0; Edge <= 1; Edge++ ) + { + for( New = 0; New < 8; New++ ) + { + for( Old = 0; Old < 8; Old++ ) + { + if( New != Old ) FirstPass(); Label++; + SecondPass(); Label++; + } + } + } + + // 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" ); + Next2(); + + // entry point + printf( "_NextGen: push ds bp si di\n" "cld\n" ); + + if( WIDTH * HEIGHT > LIST_LIMIT ) Seg = "seg _CHANGE"; + + printf( "mov ax,%s\n", Seg ); + printf( "mov es,ax\n" ); + + #ifndef NODRAW + printf( "mov ax,0A000h\n" ); + printf( "mov fs,ax\n" ); + #endif + + printf( "mov si,Change1\n" ); + printf( "mov dl,0\n" ); + Next1(); + + printf( "LIFE ends\nend\n" ); + } +``` \ No newline at end of file diff --git a/18-04.md b/18-04.md index 32ee990..bd57e88 100644 --- a/18-04.md +++ b/18-04.md @@ -12,155 +12,161 @@ pages: 361-365 **LISTING 18.3 MAIN.C** - // MAIN.C - // - // David Stafford - // +```c +// MAIN.C +// +// David Stafford +// - #include - #include - #include - #include - #include - #include "life.h" +#include +#include +#include +#include +#include +#include "life.h" - // functions in VIDEO.C - void enter_display_mode( void ); - void exit_display_mode( void ); - void show_text( int x, int y, char *text ); +// functions in VIDEO.C +void enter_display_mode( void ); +void exit_display_mode( void ); +void show_text( int x, int y, char *text ); - void InitCellmap( void ) +void InitCellmap( void ) + { + unsigned int i, j, t, x, y, init; + + for( init = (HEIGHT * WIDTH * 3) / 2; init; init— ) + { + x = random( WIDTH * 3 ); + y = random( HEIGHT ); + + CellMap[ (y * WIDTH) + x / 3 ] |= 0x1000 << (2 - (x % 3)); + } + + for( i = j = 0; i < WIDTH * HEIGHT; i++ ) + { + if( CellMap[ i ] & 0x7000 ) { - unsigned int i, j, t, x, y, init; - - for( init = (HEIGHT * WIDTH * 3) / 2; init; init— ) - { - x = random( WIDTH * 3 ); - y = random( HEIGHT ); - - CellMap[ (y * WIDTH) + x / 3 ] |= 0x1000 << (2 - (x % 3)); - } - - for( i = j = 0; i < WIDTH * HEIGHT; i++ ) - { - if( CellMap[ i ] & 0x7000 ) - { - ChangeList1[ j++ ] = (short)&CellMap[ i ]; - } - } - - NextGen(); // Set cell states, prime the pump. + ChangeList1[ j++ ] = (short)&CellMap[ i ]; } + } - void main( void ) - { - unsigned long generation = 0; - char gen_text[ 80 ]; - long start_time, end_time; - unsigned int seed; + NextGen(); // Set cell states, prime the pump. + } - printf( "Seed (0 for random seed): " ); - scanf( "%d", &seed ); - if( seed == 0 ) seed = (unsigned) time(NULL); - srand( seed ); +void main( void ) + { + unsigned long generation = 0; + char gen_text[ 80 ]; + long start_time, end_time; + unsigned int seed; - #ifndef NODRAW - enter_display_mode(); - show_text( 0, 10, "Generation:" ); - #endif + printf( "Seed (0 for random seed): " ); + scanf( "%d", &seed ); + if( seed == 0 ) seed = (unsigned) time(NULL); + srand( seed ); - InitCellmap(); // randomly initialize cell map + #ifndef NODRAW + enter_display_mode(); + show_text( 0, 10, "Generation:" ); + #endif - _bios_timeofday( _TIME_GETCLOCK, &start_time ); + InitCellmap(); // randomly initialize cell map - do - { - NextGen(); - generation++; + _bios_timeofday( _TIME_GETCLOCK, &start_time ); - #ifndef NOCOUNTER - sprintf( gen_text, "%10lu", generation ); - show_text( 0, 12, gen_text ); - #endif - } - #ifdef GEN - while( generation < GEN ); - #else - while( !kbhit() ); - #endif + do + { + NextGen(); + generation++; - _bios_timeofday( _TIME_GETCLOCK, &end_time ); - end_time -= start_time; + #ifndef NOCOUNTER + sprintf( gen_text, "%10lu", generation ); + show_text( 0, 12, gen_text ); + #endif + } + #ifdef GEN + while( generation < GEN ); + #else + while( !kbhit() ); + #endif - #ifndef NODRAW - getch(); // clear keypress - exit_display_mode(); - #endif + _bios_timeofday( _TIME_GETCLOCK, &end_time ); + end_time -= start_time; - 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 ); - } + #ifndef NODRAW + getch(); // clear keypress + 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", + (double)generation / (double)end_time * 18.2 ); + } +``` **LISTING 18.4 VIDEO.C** - /* VGA mode 13h functions for Game of Life. - Tested with Borland C++. */ - #include - #include - #include +```c +/* VGA mode 13h functions for Game of Life. + Tested with Borland C++. */ +#include +#include +#include - #define TEXT_X_OFFSET 28 - #define SCREEN_WIDTH_IN_BYTES 320 +#define TEXT_X_OFFSET 28 +#define SCREEN_WIDTH_IN_BYTES 320 - #define SCREEN_SEGMENT 0xA000 +#define SCREEN_SEGMENT 0xA000 - /* Mode 13h mode-set function. */ - void enter_display_mode() - { - union REGS regset; +/* Mode 13h mode-set function. */ +void enter_display_mode() +{ + union REGS regset; - regset.x.ax = 0x0013; - int86(0x10, ®set, ®set); - } + regset.x.ax = 0x0013; + int86(0x10, ®set, ®set); +} - /* Text mode mode-set function. */ - void exit_display_mode() - { - union REGS regset; +/* Text mode mode-set function. */ +void exit_display_mode() +{ + union REGS regset; - regset.x.ax = 0x0003; - int86(0x10, ®set, ®set); - } + regset.x.ax = 0x0003; + int86(0x10, ®set, ®set); +} - /* Text display function. Offsets text to non-graphics area of - screen. */ - void show_text(int x, int y, char *text) - { - gotoxy(TEXT_X_OFFSET + x, y); - puts(text); - } +/* Text display function. Offsets text to non-graphics area of + screen. */ +void show_text(int x, int y, char *text) +{ + gotoxy(TEXT_X_OFFSET + x, y); + puts(text); +} +``` **LISTING 18.5 LIFE.H** - void far NextGen( void ); +```c +void far NextGen( void ); - extern unsigned short CellMap[]; - extern unsigned short far ChangeList1[]; +extern unsigned short CellMap[]; +extern unsigned short far ChangeList1[]; - #define LEFT (-2) - #define RIGHT (+2) - #define UP (WIDTH * LEFT) - #define DOWN (WIDTH * RIGHT) - #define UPPERLEFT (UP + LEFT) - #define UPPERRIGHT (UP + RIGHT) - #define LOWERLEFT (DOWN + LEFT) - #define LOWERRIGHT (DOWN + RIGHT) - #define WRAPLEFT (RIGHT * (WIDTH - 1)) - #define WRAPRIGHT (LEFT * (WIDTH - 1)) - #define WRAPUP (DOWN * (HEIGHT - 1)) - #define WRAPDOWN (UP * (HEIGHT - 1)) +#define LEFT (-2) +#define RIGHT (+2) +#define UP (WIDTH * LEFT) +#define DOWN (WIDTH * RIGHT) +#define UPPERLEFT (UP + LEFT) +#define UPPERRIGHT (UP + RIGHT) +#define LOWERLEFT (DOWN + LEFT) +#define LOWERRIGHT (DOWN + RIGHT) +#define WRAPLEFT (RIGHT * (WIDTH - 1)) +#define WRAPRIGHT (LEFT * (WIDTH - 1)) +#define WRAPUP (DOWN * (HEIGHT - 1)) +#define WRAPDOWN (UP * (HEIGHT - 1)) +``` ### Keeping Track of Change with a Change List {#Heading5} diff --git a/18-05.md b/18-05.md index 369f055..dbedcde 100644 --- a/18-05.md +++ b/18-05.md @@ -103,9 +103,11 @@ and branching to the routine at that address. As with everything in this amazing program, this represents the least possible work to accomplish the desired result—just three instructions: - mov dh,[bp+1] - or dh,1 - jmp dx +```nasm +mov dh,[bp+1] +or dh,1 +jmp dx +``` These suffice to select the proper, minimum-work code to process the next cell triplet that has changed, and all potentially affected diff --git a/19-02.md b/19-02.md index f76eed1..be60a07 100644 --- a/19-02.md +++ b/19-02.md @@ -102,13 +102,15 @@ pipelines mean that an AGI can now slow down execution of an instruction 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 - mov ecx,[ebx] ;V-pipe cycle 1 - add ebx,4 ;U-pipe cycle 2 - mov [edx],ecx ;V-pipe cycle 3 - ; due to AGI - ; (would have been - ; V-pipe cycle 2) +```nasm +add edx,4 ;U-pipe cycle 1 +mov ecx,[ebx] ;V-pipe cycle 1 +add ebx,4 ;U-pipe cycle 2 +mov [edx],ecx ;V-pipe cycle 3 + ; due to AGI + ; (would have been + ; V-pipe cycle 2) +``` takes three cycles rather than the two cycles it should take, because EDX was modified on cycle 1 and an attempt was made to use it on cycle @@ -116,10 +118,12 @@ two, before the AGI had time to clear—even though there are two instructions between the instructions that are actually involved in the AGI. Rearranging the code like - mov ecx,[ebx] ;U-pipe cycle 1 - add ebx,4 ;V-pipe cycle 1 - mov [edx+4],ecx ;U-pipe cycle 2 - add edx,4 ;V-pipe cycle 2 +```nasm +mov ecx,[ebx] ;U-pipe cycle 1 +add ebx,4 ;V-pipe cycle 1 +mov [edx+4],ecx ;U-pipe cycle 2 +add edx,4 ;V-pipe cycle 2 +``` makes it functionally identical, but cuts the cycles to 2—a 50 percent improvement. Clearly, avoiding AGIs becomes a much more challenging and diff --git a/19-03.md b/19-03.md index fb01f46..929f45a 100644 --- a/19-03.md +++ b/19-03.md @@ -38,9 +38,11 @@ word-aligned, dword operands should be dword-aligned, and qword operands (double-precision variables) should be qword-aligned. Spanning a dword boundary, as in - mov ebx,3 - : - mov eax,[ebx] +```nasm +mov ebx,3 + : +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 diff --git a/20-02.md b/20-02.md index f45758b..d0611d1 100644 --- a/20-02.md +++ b/20-02.md @@ -40,43 +40,45 @@ waits until the first instruction is done, then executes in the U-pipe, possibly pairing with the next instruction in line if all pairing conditions are met. - MOV reg,reg (1 cycle) - mem,reg (1 cycle) - reg,mem (1 cycle) - reg,immediate (1 cycle) - mem,immediate (1 cycle)† +```nasm +MOV reg,reg (1 cycle) + mem,reg (1 cycle) + reg,mem (1 cycle) + reg,immediate (1 cycle) + mem,immediate (1 cycle)† - AND/OR/XOR/ADD/SUB reg,reg (1 cycle) - mem,reg (3 cycles) - reg,mem (2 cycles) - reg,immediate (1 cycle) - mem,immediate (3 cycles)† +AND/OR/XOR/ADD/SUB reg,reg (1 cycle) + mem,reg (3 cycles) + reg,mem (2 cycles) + reg,immediate (1 cycle) + mem,immediate (3 cycles)† - INC/DEC reg (1 cycle) - mem (3 cycles) +INC/DEC reg (1 cycle) + mem (3 cycles) - CMP reg,reg (1 cycle) - mem,reg (2 cycles) - reg,mem (2 cycles) - reg,immediate (1 cycle) - mem,immediate (2 cycles)† +CMP reg,reg (1 cycle) + mem,reg (2 cycles) + reg,mem (2 cycles) + reg,immediate (1 cycle) + mem,immediate (2 cycles)† - TEST reg,reg (1 cycle) - EAX,immediate (1 cycle) +TEST reg,reg (1 cycle) + EAX,immediate (1 cycle) - PUSH/POP reg (1 cycle) - immediate (1 cycle) +PUSH/POP reg (1 cycle) + immediate (1 cycle) - LEA reg,mem (1 cycle) +LEA reg,mem (1 cycle) - JCC near (1 cycle if predicted correctly; - 5 cycles otherwise in V-pipe, - 4 cycles otherwise in U-pipe) +JCC near (1 cycle if predicted correctly; + 5 cycles otherwise in V-pipe, + 4 cycles otherwise in U-pipe) - JMP/CALL near (1 cycle if predicted correctly; - 3 cycles otherwise) +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.** @@ -100,41 +102,43 @@ each of 2 cycles, for 2\*0.5 = 1 cycle total execution time), as shown in Figure 20.3—a full cycle *faster* than **PUSH [*mem*]**, which takes 2 cycles. - MOV reg,reg (1 cycle) - mem,reg (1 cycle) - reg,mem (1 cycle) - reg,immediate (1 cycle) - mem,immediate (1 cycle)† +```nasm +MOV reg,reg (1 cycle) + mem,reg (1 cycle) + reg,mem (1 cycle) + reg,immediate (1 cycle) + mem,immediate (1 cycle)† - AND/OR/XOR/ADD/SUB/ADC/SBB reg,reg (1 cycle) - mem,reg (3 cycles) - reg,mem (2 cycles) - reg,immediate (1 cycle) - mem,immediate (3 cycles)† +AND/OR/XOR/ADD/SUB/ADC/SBB reg,reg (1 cycle) + mem,reg (3 cycles) + reg,mem (2 cycles) + reg,immediate (1 cycle) + mem,immediate (3 cycles)† - INC/DEC reg (1 cycle) - mem (3 cycles) +INC/DEC reg (1 cycle) + mem (3 cycles) - CMP reg,reg (1 cycle) - mem,reg (2 cycles) - reg,mem (2 cycles) - reg,immediate (1 cycle) - mem,immediate (2 cycles)† +CMP reg,reg (1 cycle) + mem,reg (2 cycles) + reg,mem (2 cycles) + reg,immediate (1 cycle) + mem,immediate (2 cycles)† - TEST reg,reg (1 cycle) - EAX,immediate (1 cycle) +TEST reg,reg (1 cycle) + EAX,immediate (1 cycle) - PUSH/POP reg (1 cycle) - immediate (1 cycle) +PUSH/POP reg (1 cycle) + immediate (1 cycle) - LEA reg,mem (1 cycle) +LEA reg,mem (1 cycle) - SHL/SHR/SAL/SAR reg,immediate (1 cycle)†† +SHL/SHR/SAL/SAR reg,immediate (1 cycle)†† - ROL/ROR/RCL/RCR reg,1 (1 cycle) +ROL/ROR/RCL/RCR reg,1 (1 cycle) +``` - † Can't pair if address contains a displacement - †† Includes shift-by-1 forms of instructions +† 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 V-pipe-executable instructions to execute simultaneously (pair) in the @@ -155,25 +159,33 @@ 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, - push dword ptr [esi] +```nasm +push dword ptr [esi] +``` is one byte smaller than this sequence: - mov eax,[esi] - push eax +```nasm +mov eax,[esi] +push eax +``` ![**Figure 20.3**  *Pushing a value from memory effectively in one cycle.*](images/20-03.jpg) A more telling example is the following - add [MemVar],eax +```nasm +add [MemVar],eax +``` versus the equivalent: - mov edx,[MemVar] - add edx,eax - mov [MemVar],edx +```nasm +mov edx,[MemVar] +add edx,eax +mov [MemVar],edx +``` The single complex instruction takes 3 cycles and is 6 bytes long; with proper sequencing, interleaving the simple instructions with other diff --git a/20-03.md b/20-03.md index d86b9f8..4377dbb 100644 --- a/20-03.md +++ b/20-03.md @@ -79,23 +79,29 @@ the same values for address bits 2, 3, and 4 (fall in the same bank) in tight loops, and you should also avoid sequences like - mov bl,[esi] - mov bh,[esi+1] +```nasm +mov bl,[esi] +mov bh,[esi+1] +``` because both operands will generally be in the same bank. An alternative is to place another instruction between the two instructions that access the same bank, as in this sequence: - mov bl,[esi] - mov edi,edx - mov bh,[esi+1] +```nasm +mov bl,[esi] +mov edi,edx +mov bh,[esi+1] +``` By the way, the reason a code sequence that takes two instructions to load a single word is attractive in a 32-bit segment is because it takes only one cycle when the two instructions can be paired with other instructions; by contrast, the obvious way of loading BX - mov bx,[esi] +```nasm +mov bx,[esi] +``` 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 @@ -116,5 +122,7 @@ but that turns out to not always be the case. Two two-cycle instructions execute in two cycles, so it's okay to pair two instructions such as these: - add esi,[SourceSkip] ;U-pipe cycles 1 and 2 - add edi,[DestinationSkip] ;V-pipe cycles 1 and 2 +```nasm +add esi,[SourceSkip] ;U-pipe cycles 1 and 2 +add edi,[DestinationSkip] ;V-pipe cycles 1 and 2 +``` diff --git a/20-04.md b/20-04.md index 706dc29..e762a9d 100644 --- a/20-04.md +++ b/20-04.md @@ -41,13 +41,17 @@ it's the only way to get both pipes running at capacity. 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 +```nasm +and [ebx],al +``` into - mov dl,[ebx] - and dl,al - mov [ebx],dl +```nasm +mov dl,[ebx] +and dl,al +mov [ebx],dl +``` and be done with it. The problem here is one of dependency. Before the Pentium can execute **AND DL,AL,**, it must first know what is in DL, diff --git a/21-01.md b/21-01.md index 37d19e3..064d627 100644 --- a/21-01.md +++ b/21-01.md @@ -65,7 +65,9 @@ areas of AGIs and register dependencies. AGIs are *Address Generation Interlocks*, a fancy way of saying that if a register is used to address memory, as is EBX in this instruction - mov [ebx],eax +```nasm +mov [ebx],eax +``` and the value of the register is not set far enough ahead for the Pentium to perform the addressing calculations before the instruction @@ -101,14 +103,16 @@ 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: - push ebx ;U-pipe cycle 1 - mov ebx,[Ptr] ;V-pipe cycle 1 - and ebx,ebx ;U-pipe cycle 2 - jz short IsNull ;V-pipe cycle 2 - mov eax,[ebx] ;U-pipe cycle 3 AGI stall - mov edx,[ebp-8] ;V-pipe cycle 3 lockstep idle - ;U-pipe cycle 4 mov eax,[ebx] - ;V-pipe cycle 4 mov edx,[ebp-8] +```nasm +push ebx ;U-pipe cycle 1 +mov ebx,[Ptr] ;V-pipe cycle 1 +and ebx,ebx ;U-pipe cycle 2 +jz short IsNull ;V-pipe cycle 2 +mov eax,[ebx] ;U-pipe cycle 3 AGI stall +mov edx,[ebp-8] ;V-pipe cycle 3 lockstep idle + ;U-pipe cycle 4 mov eax,[ebx] + ;V-pipe cycle 4 mov edx,[ebp-8] +``` This commonplace code loses a U-pipe cycle to the AGI caused by **AND EBX,EBX**, followed by the attempt two instructions later to use EBX to diff --git a/21-02.md b/21-02.md index 857d9a6..f7d39ea 100644 --- a/21-02.md +++ b/21-02.md @@ -15,16 +15,22 @@ stack pointer. Implicit modifiers of ESP, such as **PUSH** and **POP**, are special-cased so you don't have to worry about AGIs. However, if you explicitly modify ESP with this instruction - sub esp,100h +```nasm +sub esp,100h +``` for example, or with the popular - mov esp,ebp +```nasm +mov esp,ebp +``` you can then get AGIs if you attempt to use ESP to address memory, either explicitly with instructions like this one - moveax,[esp+20h] +```nasm +moveax,[esp+20h] +``` or via **PUSH**, **POP**, or other instructions that implicitly use ESP as an addressing register. @@ -32,25 +38,31 @@ as an addressing register. On the 486, any instruction that had both a constant value and an addressing displacement, such as - mov dword ptr [ebp+16],1 +```nasm +mov dword ptr [ebp+16],1 +``` suffered a 1-cycle penalty, taking a total of 2 cycles. Such instructions take only one cycle on the Pentium, but they cannot pair, so they're still the most expensive sort of **MOV**. Knowing this can speed up something as simple as zeroing two memory variables, as in - sub eax,eax ;U-pipe 1 - ;any V-pipe pairable - ; instruction can go here, - ; or SUB could be in V-pipe - mov [MemVar1],eax ;U-pipe 2 - mov [MemVar2],eax ;V-pipe 2 +```nasm +sub eax,eax ;U-pipe 1 + ;any V-pipe pairable + ; instruction can go here, + ; or SUB could be in V-pipe +mov [MemVar1],eax ;U-pipe 2 +mov [MemVar2],eax ;V-pipe 2 +``` which should never be slower and should potentially be 0.5 cycles faster, and six bytes smaller than this sequence: - mov [MemVar1],0 ;U-pipe 1 - mov [MemVar2],0 ;U-pipe 2 +```nasm +mov [MemVar1],0 ;U-pipe 1 +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 @@ -66,10 +78,12 @@ 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 - inc eax ;U-pipe cycle 1 - ;V-pipe idle cycle 1 - ; due to dependency - and ebx,eax ;U-pipe cycle 2 +```nasm +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, @@ -86,10 +100,12 @@ to write to the same register on the same cycle. While that may not seem like a particularly useful operation in general, it can happen when subregisters are being set, as in the following - sub eax,eax ;U-pipe cycle 1 - ;V-pipe idle cycle 1 - ; due to register contention - mov al,[Var] ;U-pipe cycle 2 +```nasm +sub eax,eax ;U-pipe cycle 1 + ;V-pipe idle cycle 1 + ; due to register contention +mov al,[Var] ;U-pipe cycle 2 +``` where an attempt is made to set both EAX and its AL subregister on the same cycle. Write-after-write contention implies that the two @@ -103,8 +119,10 @@ Intel has special-cased some very useful exceptions to register contention. Happily, write-after-read operations do *not* cause contention. Such operations, as in - mov eax,edx ;U-pipe cycle 1 - sub edx,edxX ;V-pipe cycle 1 +```nasm +mov eax,edx ;U-pipe cycle 1 +sub edx,edxX ;V-pipe cycle 1 +``` are free of charge. @@ -117,12 +135,14 @@ register contention on ESP—but not AGIs—with instructions that use ESP explicitly, such as **MOV EAX,[ESP+4]**.) Without this special case, the following sequence would hardly use the V-pipe at all: - mov eax,[MemVar] ;U-pipe cycle 1 - push esi ;V-pipe cycle 1 - push eax ;U-pipe cycle 2 - push edi ;V-pipe cycle 2 - push ebx ;U-pipe cycle 3 - call FooTilde ;V-pipe cycle 3 +```nasm +mov eax,[MemVar] ;U-pipe cycle 1 +push esi ;V-pipe cycle 1 +push eax ;U-pipe cycle 2 +push edi ;V-pipe cycle 2 +push ebx ;U-pipe cycle 3 +call FooTilde ;V-pipe cycle 3 +``` But in fact, all the instructions pair, even though ESP is modified five times in the space of six instructions. @@ -133,11 +153,13 @@ pair only in the V-pipe: branches. Any near call or conditional or unconditional near jump can execute in the V-pipe paired with any pairable U-pipe instruction, as illustrated by this sequence: - LoopTop: - mov [esi],eax ;U-pipe cycle 1 - add esi,4 ;V-pipe cycle 1 - dec ecx ;U-pipe cycle 2 - jnz LoopTop ;V-pipe cycle 2 +```nasm +LoopTop: + mov [esi],eax ;U-pipe cycle 1 + add esi,4 ;V-pipe cycle 1 + 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 runs alone, with the V-pipe idle. If a call or jump is correctly @@ -159,12 +181,14 @@ instruction, the other instructions will go through different pipes than previously, and cause the loop as a whole to take 50 percent longer, even though we only added 25 percent more cycles: - LoopTop: - inc edx ;U-pipe cycle 1 - mov [esi],eax ;V-pipe cycle 1 - add esi,4 ;U-pipe cycle 2 - dec ecx ;V-pipe cycle 2 - jnz LoopTop ;U-pipe cycle 3 - ;V-pipe idle cycle 3 - ; because JNZ can't - ; pair in the U-pipe +```nasm +LoopTop: + inc edx ;U-pipe cycle 1 + mov [esi],eax ;V-pipe cycle 1 + add esi,4 ;U-pipe cycle 2 + dec ecx ;V-pipe cycle 2 + jnz LoopTop ;U-pipe cycle 3 + ;V-pipe idle cycle 3 + ; because JNZ can't + ; pair in the U-pipe +``` diff --git a/21-03.md b/21-03.md index 5f7cbc9..47181ff 100644 --- a/21-03.md +++ b/21-03.md @@ -40,34 +40,36 @@ right? **LISTING 21.1 L21-1.ASM** - ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer - ; starting at ESI, of length ECX words. - ; Returns checksum in AX. - ; ECX and ESI destroyed. - ; All cycle counts assume 32-bit protected mode. - ; Assumes buffer length > 0. - ; Note that timing indicates that the pipe sequence and - ; cycle counts shown (based on documented execution rules) - ; differ from the actual execution sequence and cycle counts; - ; this loop has been measured to execute in 5 cycles; apparently, - ; the 1st half of ADD somehow pairs with the prefix byte, or the - ; refix byte gets executed ahead of time. +```nasm +; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer +; starting at ESI, of length ECX words. +; Returns checksum in AX. +; ECX and ESI destroyed. +; All cycle counts assume 32-bit protected mode. +; Assumes buffer length > 0. +; Note that timing indicates that the pipe sequence and +; cycle counts shown (based on documented execution rules) +; differ from the actual execution sequence and cycle counts; +; this loop has been measured to execute in 5 cycles; apparently, +; the 1st half of ADD somehow pairs with the prefix byte, or the +; refix byte gets executed ahead of time. - sub ax,ax ;initialize the checksum + sub ax,ax ;initialize the checksum - ckloop: - add ax,[esi] ;cycle 1 U-pipe prefix byte - ;cycle 1 V-pipe idle (no pairing w/prefix) - ;cycle 2 U-pipe 1st half of ADD - ;cycle 2 V-pipe idle (register contention) - ;cycle 3 U-pipe 2nd half of ADD - ;cycle 3 V-pipe idle (register contention) - adc ax,0 ;cycle 4 U-pipe prefix byte - ;cycle 4 V-pipe idle (no pairing w/prefix) - ;cycle 5 U-pipe ADC AX,0 - add esi,2 ;cycle 5 V-pipe - dec ecx ;cycle 6 U-pipe - jnz ckloop ;cycle 6 V-pipe +ckloop: + add ax,[esi] ;cycle 1 U-pipe prefix byte + ;cycle 1 V-pipe idle (no pairing w/prefix) + ;cycle 2 U-pipe 1st half of ADD + ;cycle 2 V-pipe idle (register contention) + ;cycle 3 U-pipe 2nd half of ADD + ;cycle 3 V-pipe idle (register contention) + adc ax,0 ;cycle 4 U-pipe prefix byte + ;cycle 4 V-pipe idle (no pairing w/prefix) + ;cycle 5 U-pipe ADC AX,0 + add esi,2 ;cycle 5 V-pipe + dec ecx ;cycle 6 U-pipe + jnz ckloop ;cycle 6 V-pipe +``` 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 diff --git a/21-04.md b/21-04.md index c0cdcef..4133635 100644 --- a/21-04.md +++ b/21-04.md @@ -27,32 +27,34 @@ same cache data bank, as discussed in the last chapter). **LISTING 21.2 L21-2.ASM** - ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer - ; starting at ESI, of length ECX words. - ; Returns checksum in AX. - ; High word of EAX, DX, ECX and ESI destroyed. - ; All cycle counts assume 32-bit protected mode. - ; Assumes buffer length > 0. +```nasm +; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer +; starting at ESI, of length ECX words. +; Returns checksum in AX. +; High word of EAX, DX, ECX and ESI destroyed. +; All cycle counts assume 32-bit protected mode. +; Assumes buffer length > 0. - sub eax,eax ;initialize the checksum - mov dx,[esi] ;first word to checksum - 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 + sub eax,eax ;initialize the checksum + mov dx,[esi] ;first word to checksum + 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 - ckloop: - add al,dl ;cycle 1 U-pipe - mov dl,[esi] ;cycle 1 V-pipe - adc ah,dh ;cycle 2 U-pipe - mov dh,[esi+1] ;cycle 2 V-pipe - adc eax,0 ;cycle 3 U-pipe - add esi,2 ;cycle 3 V-pipe - dec ecx ;cycle 4 U-pipe - jnz ckloop ;cycle 4 V-pipe +ckloop: + add al,dl ;cycle 1 U-pipe + mov dl,[esi] ;cycle 1 V-pipe + adc ah,dh ;cycle 2 U-pipe + mov dh,[esi+1] ;cycle 2 V-pipe + adc eax,0 ;cycle 3 U-pipe + add esi,2 ;cycle 3 V-pipe + dec ecx ;cycle 4 U-pipe + jnz ckloop ;cycle 4 V-pipe - ckloopend: - add ax,dx ;checksum the last word - adc eax,0 +ckloopend: + add ax,dx ;checksum the last word + adc eax,0 +``` Listing 21.3 is a more sophisticated attempt to speed up the checksum calculation. Here we see a hallmark of Pentium optimization: two @@ -68,52 +70,54 @@ placement of **ADD ESI,4** to avoid an AGI. **LISTING 21.3 L21-3.ASM** - ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer - ; starting at ESI, of length ECX words. - ; Returns checksum in AX. - ; High word of EAX, BX, EDX, ECX and ESI destroyed. - ; All cycle counts assume 32-bit protected mode. - ; Assumes buffer length > 0. +```nasm +; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer +; starting at ESI, of length ECX words. +; Returns checksum in AX. +; High word of EAX, BX, EDX, ECX and ESI destroyed. +; All cycle counts assume 32-bit protected mode. +; Assumes buffer length > 0. - sub eax,eax ;initialize the checksum - sub edx,edx ;prepare for later ORing - 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 - add esi,2 ;point to the next word - ckloopsetup: - mov dx,[esi] ;load most of 1st word to - mov bl,[esi+2] ; checksum (last byte loaded in loop) - dec ecx ;any more dwords to checksum? - jz short ckloopend ;no + sub eax,eax ;initialize the checksum + sub edx,edx ;prepare for later ORing + 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 + add esi,2 ;point to the next word +ckloopsetup: + mov dx,[esi] ;load most of 1st word to + mov bl,[esi+2] ; checksum (last byte loaded in loop) + dec ecx ;any more dwords to checksum? + jz short ckloopend ;no - ckloop: - mov bh,[esi+3] ;cycle 1 U-pipe - add esi,4 ;cycle 1 V-pipe - shl ebx,16 ;cycle 2 U-pipe - ;cycle 2 V-pipe idle - ; (register contention) - or ebx,edx ;cycle 3 U-pipe - mov dl,[esi] ;cycle 3 V-pipe - add eax,ebx ;cycle 4 U-pipe - mov bl,[esi+2] ;cycle 4 V-pipe - adc eax,0 ;cycle 5 U-pipe - mov dh,[esi+1] ;cycle 5 V-pipe - dec ecx ;cycle 6 U-pipe - jnz ckloop ;cycle 6 V-pipe +ckloop: + mov bh,[esi+3] ;cycle 1 U-pipe + add esi,4 ;cycle 1 V-pipe + shl ebx,16 ;cycle 2 U-pipe + ;cycle 2 V-pipe idle + ; (register contention) + or ebx,edx ;cycle 3 U-pipe + mov dl,[esi] ;cycle 3 V-pipe + add eax,ebx ;cycle 4 U-pipe + mov bl,[esi+2] ;cycle 4 V-pipe + adc eax,0 ;cycle 5 U-pipe + mov dh,[esi+1] ;cycle 5 V-pipe + dec ecx ;cycle 6 U-pipe + jnz ckloop ;cycle 6 V-pipe - ckloopend: - mov bh,[esi+3] ;checksum the last dword - add ax,dx - adc ax,bx - adc ax,0 +ckloopend: + mov bh,[esi+3] ;checksum the last dword + add ax,dx + adc ax,bx + adc ax,0 - mov edx,eax ;compress the 32-bit checksum - shr edx,16 ; into a 16-bit checksum - add ax,dx - adc eax,0 - ckloopdone: + mov edx,eax ;compress the 32-bit checksum + shr edx,16 ; into a 16-bit checksum + add ax,dx + adc eax,0 +ckloopdone: +``` 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 diff --git a/21-05.md b/21-05.md index 41420f2..8c8f514 100644 --- a/21-05.md +++ b/21-05.md @@ -12,36 +12,38 @@ pages: 409-411 **LISTING 21.4 L21-4.ASM** - ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer - ; starting at ESI, of length ECX words. - ; Returns checksum in AX. - ; High word of EAX, ECX, EDX, and ESI destroyed. - ; All cycle counts assume 32-bit protected mode. - ; Assumes buffer starts on a dword boundary, is a dword multiple - ; in length, and length > 0. +```nasm +; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer +; starting at ESI, of length ECX words. +; Returns checksum in AX. +; High word of EAX, ECX, EDX, and ESI destroyed. +; All cycle counts assume 32-bit protected mode. +; Assumes buffer starts on a dword boundary, is a dword multiple +; in length, and length > 0. - sub eax,eax ;initialize the checksum - 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 - jz short ckloopend ;only 1 checksum to do + sub eax,eax ;initialize the checksum + 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 + jz short ckloopend ;only 1 checksum to do - ckloop: - add eax,edx ;cycle 1 U-pipe - mov edx,[esi] ;cycle 1 V-pipe - adc eax,0 ;cycle 2 U-pipe - add esi,4 ;cycle 2 V-pipe - dec ecx ;cycle 3 U-pipe - jnz ckloop ;cycle 3 V-pipe +ckloop: + add eax,edx ;cycle 1 U-pipe + mov edx,[esi] ;cycle 1 V-pipe + adc eax,0 ;cycle 2 U-pipe + add esi,4 ;cycle 2 V-pipe + dec ecx ;cycle 3 U-pipe + jnz ckloop ;cycle 3 V-pipe - ckloopend: - add eax,edx ;checksum the last dword - adc eax,0 - mov edx,eax ;compress the 32-bit checksum - shr edx,16 ; into a 16-bit checksum - add ax,dx - adc eax,0 +ckloopend: + add eax,edx ;checksum the last dword + adc eax,0 + mov edx,eax ;compress the 32-bit checksum + shr edx,16 ; into a 16-bit checksum + add ax,dx + adc eax,0 +``` 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. @@ -53,46 +55,48 @@ more registers. **LISTING 21.5 L21-5.ASM** - ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer - ; starting at ESI, of length ECX words. - ; Returns checksum in AX. - ; High word of EAX, EBX, ECX, EDX, and ESI destroyed. - ; All cycle counts assume 32-bit protected mode. - ; Assumes buffer starts on a dword boundary, is a dword multiple - ; in length, and length > 0. +```nasm +; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer +; starting at ESI, of length ECX words. +; Returns checksum in AX. +; High word of EAX, EBX, ECX, EDX, and ESI destroyed. +; All cycle counts assume 32-bit protected mode. +; Assumes buffer starts on a dword boundary, is a dword multiple +; in length, and length > 0. - sub eax,eax ;initialize the checksum - 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 - add esi,4 ;point to the next dword - 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 - jz short ckloopend ;only 1 checksum to do - add esi,8 ;point to the next dword + sub eax,eax ;initialize the checksum + 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 + add esi,4 ;point to the next dword +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 + jz short ckloopend ;only 1 checksum to do + add esi,8 ;point to the next dword - ckloop: - add eax,edx ;cycle 1 U-pipe - mov edx,[esi] ;cycle 1 V-pipe - adc eax,ebx ;cycle 2 U-pipe - mov ebx,[esi+4] ;cycle 2 V-pipe - adc eax,0 ;cycle 3 U-pipe - add esi,8 ;cycle 3 V-pipe - dec ecx ;cycle 4 U-pipe - jnz ckloop ;cycle 4 V-pipe +ckloop: + add eax,edx ;cycle 1 U-pipe + mov edx,[esi] ;cycle 1 V-pipe + adc eax,ebx ;cycle 2 U-pipe + mov ebx,[esi+4] ;cycle 2 V-pipe + adc eax,0 ;cycle 3 U-pipe + add esi,8 ;cycle 3 V-pipe + dec ecx ;cycle 4 U-pipe + jnz ckloop ;cycle 4 V-pipe - ckloopend: - add eax,edx ;checksum the last two dwords - adc eax,ebx - adc eax,0 - ckloopdone: - mov edx,eax ;compress the 32-bit checksum - shr edx,16 ; into a 16-bit checksum - add ax,dx - adc eax,0 +ckloopend: + add eax,edx ;checksum the last two dwords + adc eax,ebx + adc eax,0 +ckloopdone: + mov edx,eax ;compress the 32-bit checksum + shr edx,16 ; into a 16-bit checksum + add ax,dx + 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 diff --git a/22-01.md b/22-01.md index 52916c5..564b708 100644 --- a/22-01.md +++ b/22-01.md @@ -55,40 +55,42 @@ 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 - RetAddr dw ? ;return address - Filler dw ? ;character to fill the buffer with - Attrib dw ? ;attribute to fill the buffer with - BufSize dw ? ;number of character/attribute pairs to fill - BufOfs dw ? ;buffer offset - BufSeg dw ? ;buffer segment - EndMrk db ? ;marker for the end of the stack frame - OnStack ends - ; - ClearS proc near - 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 - cmp word ptr [bp].BufOfs,0 - je Bye - Start: cld ;make STOSW count up - mov ax,[bp].Attrib ;load AX with attribute parameter - and ax,0ff00h ;prepare for merging with fill char - mov bx,[bp].Filler ;load BX with fill char - and bx,0ffh ;prepare for merging with attribute - or ax,bx ;combine attribute and fill char - mov bx,[bp].BufOfs ;load DI with target buffer offset - mov di,bx - mov bx,[bp].BufSeg ;load ES with target buffer segment - mov es,bx - 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 - ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack - ClearS endp +```nasm +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 +BufSize dw ? ;number of character/attribute pairs to fill +BufOfs dw ? ;buffer offset +BufSeg dw ? ;buffer segment +EndMrk db ? ;marker for the end of the stack frame +OnStack ends +; +ClearS proc near + 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 + cmp word ptr [bp].BufOfs,0 + je Bye +Start: cld ;make STOSW count up + mov ax,[bp].Attrib ;load AX with attribute parameter + and ax,0ff00h ;prepare for merging with fill char + mov bx,[bp].Filler ;load BX with fill char + and bx,0ffh ;prepare for merging with attribute + or ax,bx ;combine attribute and fill char + mov bx,[bp].BufOfs ;load DI with target buffer offset + mov di,bx + mov bx,[bp].BufSeg ;load ES with target buffer segment + mov es,bx + 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 + 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 diff --git a/22-02.md b/22-02.md index 19c70fd..00112be 100644 --- a/22-02.md +++ b/22-02.md @@ -12,27 +12,29 @@ pages: 417-418 **LISTING 22.2 L22-2.ASM** - ClearS proc near - 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 - cmp word ptr [bp].BufOfs,0 - je Bye - Start: cld ;make STOSW count up - mov ax,[bp].Attrib ;load AX with attribute parameter - and ax,0ff00h ;prepare for merging with fill char - mov bx,[bp].Filler ;load BX with fill char - and bx,0ffh ;prepare for merging with attribute - or ax,bx ;combine attribute and fill char - mov di,[bp].BufOfs ;load DI with target buffer offset - mov es,[bp].BufSeg ;load ES with target buffer segment - mov cx,[bp].BufSize ;load CX with buffer size - rep stosw ;fill the buffer - Bye: - pop bp ;restore caller's BP - ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack - ClearS endp +```nasm +ClearS proc near + 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 + cmp word ptr [bp].BufOfs,0 + je Bye +Start: cld ;make STOSW count up + mov ax,[bp].Attrib ;load AX with attribute parameter + and ax,0ff00h ;prepare for merging with fill char + mov bx,[bp].Filler ;load BX with fill char + and bx,0ffh ;prepare for merging with attribute + or ax,bx ;combine attribute and fill char + mov di,[bp].BufOfs ;load DI with target buffer offset + mov es,[bp].BufSeg ;load ES with target buffer segment + mov cx,[bp].BufSize ;load CX with buffer size + rep stosw ;fill the buffer +Bye: + 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 @@ -47,27 +49,29 @@ 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 - mov bp,sp ;point to stack frame - cmp word ptr [bp].BufSeg,0 ;skip the fill if a null - jne Start ; pointer is passed - cmp word ptr [bp].BufOfs,0 - je Bye - Start: cld ;make STOSW count up - mov ax,[bp].Attrib ;load AX with attribute parameter - and ax,0ff00h ;prepare for merging with fill char - mov bx,[bp].Filler ;load BX with fill char - and bx,0ffh ;prepare for merging with attribute - or ax,bx ;combine attribute and fill char - les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer - ;segment:offset - mov cx,[bp].BufSize ;load CX with buffer size - rep stosw ;fill the buffer - Bye: - pop bp ;restore caller's BP - ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack - ClearS endp +```nasm +ClearS proc near + 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 + cmp word ptr [bp].BufOfs,0 + je Bye +Start: cld ;make STOSW count up + mov ax,[bp].Attrib ;load AX with attribute parameter + and ax,0ff00h ;prepare for merging with fill char + mov bx,[bp].Filler ;load BX with fill char + and bx,0ffh ;prepare for merging with attribute + or ax,bx ;combine attribute and fill char + les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer + ;segment:offset + mov cx,[bp].BufSize ;load CX with buffer size + rep stosw ;fill the buffer +Bye: + 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 counting. diff --git a/22-03.md b/22-03.md index 1f64e2f..5fb12f0 100644 --- a/22-03.md +++ b/22-03.md @@ -12,23 +12,25 @@ pages: 419-420 **LISTING 22.5 L22-5.ASM** - ClearS proc near - 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 - cmp word ptr [bp].BufOfs,0 - je Bye - 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 - les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer segment:offset - mov cx,[bp].BufSize ;load CX with buffer size - rep stosw ;fill the buffer - Bye: - pop bp ;restore caller's BP - ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack - ClearS endp +```nasm +ClearS proc near + 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 + cmp word ptr [bp].BufOfs,0 + je Bye +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 + les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer segment:offset + mov cx,[bp].BufSize ;load CX with buffer size + rep stosw ;fill the buffer +Bye: + 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 @@ -44,22 +46,24 @@ shown in Listing 22.6. **LISTING 22.6 L22-6.ASM** - ClearS proc near - 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 - 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 - ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack - ClearS endp +```nasm +ClearS proc near + 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 +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 + 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 subroutine by nearly 50 percent. Only 13 instructions remain. @@ -103,22 +107,24 @@ With that problem dealt with, Listing 22.7 shows the Zenned version of **LISTING 22.7 L22-7.ASM** - ClearS procnear - pop dx ;get the return address - pop ax ;put fill char into AL - pop bx ;get the attribute - mov ah,bh ;put attribute into AH - pop cx ;get the buffer size - pop di ;get the offset of the buffer origin - 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 - cld ;make STOSW count up - rep stosw ;do the string store - Bye: - jmp dx ;return to the calling code - ClearS endp +```nasm +ClearS procnear + pop dx ;get the return address + pop ax ;put fill char into AL + pop bx ;get the attribute + mov ah,bh ;put attribute into AH + pop cx ;get the buffer size + pop di ;get the offset of the buffer origin + 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 + 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 diff --git a/23-03.md b/23-03.md index 3dbc533..8e9d660 100644 --- a/23-03.md +++ b/23-03.md @@ -72,558 +72,560 @@ seen the features in action. **LISTING 23.1 L23-1.ASM** - ; Sample VGA program. - ; Animates four balls bouncing around a playfield by using - ; page flipping. Playfield is panned smoothly both horizontally - ; and vertically. - ; By Michael Abrash. - ; - stack segment para stack 'STACK' - db 512 dup(?) - stack ends - ; - MEDRES_VIDEO_MODE equ 0 ;define for 640x350 video mode - ; comment out for 640x200 mode - VIDEO_SEGMENT equ 0a000h ;display memory segment for - ; true VGA graphics modes - LOGICAL_SCREEN_WIDTH equ 672/8 ;width in bytes and height in scan - LOGICAL_SCREEN_HEIGHT equ 384 ; lines of the virtual screen - ; we'll work with - PAGE0 equ 0 ;flag for page 0 when page flipping - PAGE1 equ 1 ;flag for page 1 when page flipping - PAGE0_OFFSET equ 0 ;start offset of page 0 in VGA memory - PAGE1_OFFSET equ LOGICAL_SCREEN_WIDTH * LOGICAL_SCREEN_HEIGHT - ;start offset of page 1 (both pages - ; are 672x384 virtual screens) - BALL_WIDTH equ 24/8 ;width of ball in display memory bytes - BALL_HEIGHT equ 24 ;height of ball in scan lines - BLANK_OFFSET equ PAGE1_OFFSET * 2 ;start of blank image - ; in VGA memory - BALL_OFFSET equ BLANK_OFFSET + (BALL_WIDTH * BALL_HEIGHT) - ;start offset of ball image in VGA memory - NUM_BALLS equ 4 ;number of balls to animate - ; - ; VGA register equates. - ; - SC_INDEX equ 3c4h ;SC index register - MAP_MASK equ 2 ;SC map mask register - GC_INDEX equ 3ceh ;GC index register - GC_MODE equ 5 ;GC mode register - CRTC_INDEX equ 03d4h ;CRTC index register - START_ADDRESS_HIGH equ 0ch ;CRTC start address high byte - START_ADDRESS_LOW equ 0dh ;CRTC start address low byte - CRTC_OFFSET equ 13h ;CRTC offset register - INPUT_STATUS_1 equ 03dah ;VGA status register - VSYNC_MASK equ 08h ;vertical sync bit in status register 1 - DE_MASK equ 01h ;display enable bit in status register 1 - AC_INDEX equ 03c0h ;AC index register - HPELPAN equ 20h OR 13h ;AC horizontal pel panning register - ; (bit 7 is high to keep palette RAM - ; addressing on) - dseg segment para common 'DATA' - CurrentPage db PAGE1 ;page to draw to - CurrentPageOffset dw PAGE1_OFFSET - ; - ; Four plane's worth of multicolored ball image. - ; - BallPlane0Image label byte ;blue plane image - db 000h, 03ch, 000h, 001h, 0ffh, 080h - db 007h, 0ffh, 0e0h, 00fh, 0ffh, 0f0h - db 4 * 3 dup(000h) - db 07fh, 0ffh, 0feh, 0ffh, 0ffh, 0ffh - db 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh - db 4 * 3 dup(000h) - db 07fh, 0ffh, 0feh, 03fh, 0ffh, 0fch - db 03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h - db 4 * 3 dup(000h) - BallPlane1Image label byte ;green plane image - db 4 * 3 dup(000h) - db 01fh, 0ffh, 0f8h, 03fh, 0ffh, 0fch - db 03fh, 0ffh, 0fch, 07fh, 0ffh, 0feh - db 07fh, 0ffh, 0feh, 0ffh, 0ffh, 0ffh - db 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh - db 8 * 3 dup(000h) - db 00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h - db 001h, 0ffh, 080h, 000h, 03ch, 000h - BallPlane2Image label byte ;red plane image - db 12 * 3 dup(000h) - db 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh - db 0ffh, 0ffh, 0ffh, 07fh, 0ffh, 0feh - db 07fh, 0ffh, 0feh, 03fh, 0ffh, 0fch - db 03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h - db 00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h - db 001h, 0ffh, 080h, 000h, 03ch, 000h - BallPlane3Image label byte ;intensity on for all planes, - ; to produce high-intensity colors - db 000h, 03ch, 000h, 001h, 0ffh, 080h - db 007h, 0ffh, 0e0h, 00fh, 0ffh, 0f0h - db 01fh, 0ffh, 0f8h, 03fh, 0ffh, 0fch - db 03fh, 0ffh, 0fch, 07fh, 0ffh, 0feh - db 07fh, 0ffh, 0feh, 0ffh, 0ffh, 0ffh - db 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh - db 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh - db 0ffh, 0ffh, 0ffh, 07fh, 0ffh, 0feh - db 07fh, 0ffh, 0feh, 03fh, 0ffh, 0fch - db 03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h - db 00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h - db 001h, 0ffh, 080h, 000h, 03ch, 000h - ; - BallX dw 15, 50, 40, 70 ;array of ball x coords - BallY dw 40, 200, 110, 300 ;array of ball y coords - LastBallX dw 15, 50, 40, 70 ;previous ball x coords - LastBallY dw 40, 100, 160, 30 ;previous ball y coords - BallXInc dw 1, 1, 1, 1 ;x move factors for ball - BallYInc dw 8, 8, 8, 8 ;y move factors for ball - BallRep dw 1, 1, 1, 1 ;# times to keep moving - ; ball according to current - ; increments - BallControl dw Ball0Control, Ball1Control ;pointers to current - dw Ball2Control, Ball3Control ; locations in ball - ; control strings - BallControlString dw Ball0Control, Ball1Control ;pointers to - dw Ball2Control, Ball3Control ; start of ball - ; control strings - ; - ; Ball control strings. - ; - Ball0Control label word - dw 10, 1, 4, 10, -1, 4, 10, -1, -4, 10, 1, -4, 0 - Ball1Control label word - dw 12, -1, 1, 28, -1, -1, 12, 1, -1, 28, 1, 1, 0 - Ball2Control label word - dw 20, 0, -1, 40, 0, 1, 20, 0, -1, 0 - Ball3Control label word - dw 8, 1, 0, 52, -1, 0, 44, 1, 0, 0 - ; - ; Panning control string. - ; - ifdef MEDRES_VIDEO_MODE - PanningControlString dw 32, 1, 0, 34, 0, 1, 32, -1, 0, 34, 0, -1, 0 - else - PanningControlString dw 32, 1, 0, 184, 0, 1, 32, -1, 0, 184, 0, -1, 0 - endif - PanningControl dw PanningControlString ;pointer to current location - ; in panning control string - PanningRep dw 1 ;# times to pan according to current - ; panning increments - PanningXInc dw 1 ;x panning factor - PanningYInc dw 0 ;y panning factor - HPan db 0 ;horizontal pel panning setting - PanningStartOffset dw 0 ;start offset adjustment to produce vertical - ; panning & coarse horizontal panning - dseg ends - ; - ; Macro to set indexed register P2 of chip with index register - ; at P1 to AL. - ; - SETREG macro P1, P2 - mov dx,P1 - mov ah,al - mov al,P2 - out dx,ax - endm - ; - cseg segment para public 'CODE' - assume cs:cseg, ds:dseg - start proc near - mov ax,dseg - mov ds,ax - ; - ; Select graphics mode. - ; - ifdef MEDRES_VIDEO_MODE - mov ax,010h - else - mov ax,0eh - endif - int 10h - ; - ; ES always points to VGA memory. - ; - mov ax,VIDEO_SEGMENT - mov es,ax - ; - ; Draw border around playfield in both pages. - ; - mov di,PAGE0_OFFSET - call DrawBorder ;page 0 border - mov di,PAGE1_OFFSET - call DrawBorder ;page 1 border - ; - ; Draw all four plane's worth of the ball to undisplayed VGA memory. - ; - mov al,01h ;enable plane 0 - SETREG SC_INDEX, MAP_MASK - mov si,offset BallPlane0Image - mov di,BALL_OFFSET - mov cx,BALL_WIDTH * BALL_HEIGHT - rep movsb - mov al,02h ;enable plane 1 - SETREG SC_INDEX, MAP_MASK - mov si,offset BallPlane1Image - mov di,BALL_OFFSET - mov cx,BALL_WIDTH * BALL_HEIGHT - rep movsb - mov al,04h ;enable plane 2 - SETREG SC_INDEX, MAP_MASK - mov si,offset BallPlane2Image - mov di,BALL_OFFSET - mov cx,BALL_WIDTH * BALL_HEIGHT - rep movsb - mov al,08h ;enable plane 3 - SETREG SC_INDEX, MAP_MASK - mov si,offset BallPlane3Image - mov di,BALL_OFFSET - mov cx,BALL_WIDTH * BALL_HEIGHT - rep movsb - ; - ; Draw a blank image the size of the ball to undisplayed VGA memory. - ; - mov al,0fh ;enable all memory planes, since the - SETREG SC_INDEX, MAP_MASK ; blank has to erase all planes - mov di,BLANK_OFFSET - mov cx,BALL_WIDTH * BALL_HEIGHT - sub al,al - rep stosb - ; - ; Set VGA to write mode 1, for block copying ball and blank images. - ; - mov dx,GC_INDEX - mov al,GC_MODE - out dx,al ;point GC Index to GC Mode register - inc dx ;point to GC Data register - jmp $+2 ;delay to let bus settle - in al,dx ;get current state of GC Mode - and al,not 3 ;clear the write mode bits - or al,1 ;set the write mode field to 1 - jmp $+2 ;delay to let bus settle - out dx,al - ; - ; Set VGA offset register in words to define logical screen width. - ; - mov al,LOGICAL_SCREEN_WIDTH / 2 - SETREG CRTC_INDEX, CRTC_OFFSET - ; - ; Move the balls by erasing each ball, moving it, and - ; redrawing it, then switching pages when they're all moved. - ; - BallAnimationLoop: - mov bx,( NUM_BALLS * 2 ) - 2 - EachBallLoop: - ; - ; Erase old image of ball in this page (at location from one more earlier). - ; - mov si,BLANK_OFFSET ;point to blank image - mov cx,[LastBallX+bx] - mov dx,[LastBallY+bx] - call DrawBall - ; - ; Set new last ball location. - ; - mov ax,[BallX+bx] - mov [LastballX+bx],ax - mov ax,[BallY+bx] - mov [LastballY+bx],ax - ; - ; Change the ball movement values if it's time to do so. - ; - dec [BallRep+bx] ;has current repeat factor run out? - jnz MoveBall - mov si,[BallControl+bx] ;it's time to change movement values - lodsw ;get new repeat factor from - ; control string - and ax,ax ;at end of control string? - jnz SetNewMove - mov si,[BallControlString+bx] ;reset control string - lodsw ;get new repeat factor - SetNewMove: - mov [BallRep+bx],ax ;set new movement repeat factor - lodsw ;set new x movement increment - mov [BallXInc+bx],ax - lodsw ;set new y movement increment - mov [BallYInc+bx],ax - mov [BallControl+bx],si ;save new control string pointer - ; - ; Move the ball. - ; - MoveBall: - mov ax,[BallXInc+bx] - add [BallX+bx],ax ;move in x direction - mov ax,[BallYInc+bx] - add [BallY+bx],ax ;move in y direction - ; - ; Draw ball at new location. - ; - mov si,BALL_OFFSET ;point to ball's image - mov cx,[BallX+bx] - mov dx,[BallY+bx] - call DrawBall - ; - dec bx - dec bx - jns EachBallLoop +```nasm +; Sample VGA program. +; Animates four balls bouncing around a playfield by using +; page flipping. Playfield is panned smoothly both horizontally +; and vertically. +; By Michael Abrash. +; +stack segment para stack 'STACK' + db 512 dup(?) +stack ends +; +MEDRES_VIDEO_MODE equ 0 ;define for 640x350 video mode + ; comment out for 640x200 mode +VIDEO_SEGMENT equ 0a000h ;display memory segment for + ; true VGA graphics modes +LOGICAL_SCREEN_WIDTH equ 672/8 ;width in bytes and height in scan +LOGICAL_SCREEN_HEIGHT equ 384 ; lines of the virtual screen + ; we'll work with +PAGE0 equ 0 ;flag for page 0 when page flipping +PAGE1 equ 1 ;flag for page 1 when page flipping +PAGE0_OFFSET equ 0 ;start offset of page 0 in VGA memory +PAGE1_OFFSET equ LOGICAL_SCREEN_WIDTH * LOGICAL_SCREEN_HEIGHT + ;start offset of page 1 (both pages + ; are 672x384 virtual screens) +BALL_WIDTH equ 24/8 ;width of ball in display memory bytes +BALL_HEIGHT equ 24 ;height of ball in scan lines +BLANK_OFFSET equ PAGE1_OFFSET * 2 ;start of blank image + ; in VGA memory +BALL_OFFSET equ BLANK_OFFSET + (BALL_WIDTH * BALL_HEIGHT) + ;start offset of ball image in VGA memory +NUM_BALLS equ 4 ;number of balls to animate +; +; VGA register equates. +; +SC_INDEX equ 3c4h ;SC index register +MAP_MASK equ 2 ;SC map mask register +GC_INDEX equ 3ceh ;GC index register +GC_MODE equ 5 ;GC mode register +CRTC_INDEX equ 03d4h ;CRTC index register +START_ADDRESS_HIGH equ 0ch ;CRTC start address high byte +START_ADDRESS_LOW equ 0dh ;CRTC start address low byte +CRTC_OFFSET equ 13h ;CRTC offset register +INPUT_STATUS_1 equ 03dah ;VGA status register +VSYNC_MASK equ 08h ;vertical sync bit in status register 1 +DE_MASK equ 01h ;display enable bit in status register 1 +AC_INDEX equ 03c0h ;AC index register +HPELPAN equ 20h OR 13h ;AC horizontal pel panning register + ; (bit 7 is high to keep palette RAM + ; addressing on) +dseg segment para common 'DATA' +CurrentPage db PAGE1 ;page to draw to +CurrentPageOffset dw PAGE1_OFFSET +; +; Four plane's worth of multicolored ball image. +; +BallPlane0Image label byte ;blue plane image + db 000h, 03ch, 000h, 001h, 0ffh, 080h + db 007h, 0ffh, 0e0h, 00fh, 0ffh, 0f0h + db 4 * 3 dup(000h) + db 07fh, 0ffh, 0feh, 0ffh, 0ffh, 0ffh + db 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh + db 4 * 3 dup(000h) + db 07fh, 0ffh, 0feh, 03fh, 0ffh, 0fch + db 03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h + db 4 * 3 dup(000h) +BallPlane1Image label byte ;green plane image + db 4 * 3 dup(000h) + db 01fh, 0ffh, 0f8h, 03fh, 0ffh, 0fch + db 03fh, 0ffh, 0fch, 07fh, 0ffh, 0feh + db 07fh, 0ffh, 0feh, 0ffh, 0ffh, 0ffh + db 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh + db 8 * 3 dup(000h) + db 00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h + db 001h, 0ffh, 080h, 000h, 03ch, 000h +BallPlane2Image label byte ;red plane image + db 12 * 3 dup(000h) + db 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh + db 0ffh, 0ffh, 0ffh, 07fh, 0ffh, 0feh + db 07fh, 0ffh, 0feh, 03fh, 0ffh, 0fch + db 03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h + db 00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h + db 001h, 0ffh, 080h, 000h, 03ch, 000h +BallPlane3Image label byte ;intensity on for all planes, + ; to produce high-intensity colors + db 000h, 03ch, 000h, 001h, 0ffh, 080h + db 007h, 0ffh, 0e0h, 00fh, 0ffh, 0f0h + db 01fh, 0ffh, 0f8h, 03fh, 0ffh, 0fch + db 03fh, 0ffh, 0fch, 07fh, 0ffh, 0feh + db 07fh, 0ffh, 0feh, 0ffh, 0ffh, 0ffh + db 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh + db 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh + db 0ffh, 0ffh, 0ffh, 07fh, 0ffh, 0feh + db 07fh, 0ffh, 0feh, 03fh, 0ffh, 0fch + db 03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h + db 00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h + db 001h, 0ffh, 080h, 000h, 03ch, 000h +; +BallX dw 15, 50, 40, 70 ;array of ball x coords +BallY dw 40, 200, 110, 300 ;array of ball y coords +LastBallX dw 15, 50, 40, 70 ;previous ball x coords +LastBallY dw 40, 100, 160, 30 ;previous ball y coords +BallXInc dw 1, 1, 1, 1 ;x move factors for ball +BallYInc dw 8, 8, 8, 8 ;y move factors for ball +BallRep dw 1, 1, 1, 1 ;# times to keep moving + ; ball according to current + ; increments +BallControl dw Ball0Control, Ball1Control ;pointers to current + dw Ball2Control, Ball3Control ; locations in ball + ; control strings +BallControlString dw Ball0Control, Ball1Control ;pointers to + dw Ball2Control, Ball3Control ; start of ball + ; control strings +; +; Ball control strings. +; +Ball0Control label word + dw 10, 1, 4, 10, -1, 4, 10, -1, -4, 10, 1, -4, 0 +Ball1Control label word + dw 12, -1, 1, 28, -1, -1, 12, 1, -1, 28, 1, 1, 0 +Ball2Control label word + dw 20, 0, -1, 40, 0, 1, 20, 0, -1, 0 +Ball3Control label word + dw 8, 1, 0, 52, -1, 0, 44, 1, 0, 0 +; +; Panning control string. +; +ifdef MEDRES_VIDEO_MODE +PanningControlString dw 32, 1, 0, 34, 0, 1, 32, -1, 0, 34, 0, -1, 0 +else +PanningControlString dw 32, 1, 0, 184, 0, 1, 32, -1, 0, 184, 0, -1, 0 +endif +PanningControl dw PanningControlString ;pointer to current location + ; in panning control string +PanningRep dw 1 ;# times to pan according to current + ; panning increments +PanningXInc dw 1 ;x panning factor +PanningYInc dw 0 ;y panning factor +HPan db 0 ;horizontal pel panning setting +PanningStartOffset dw 0 ;start offset adjustment to produce vertical + ; panning & coarse horizontal panning +dseg ends +; +; Macro to set indexed register P2 of chip with index register +; at P1 to AL. +; +SETREG macro P1, P2 + mov dx,P1 + mov ah,al + mov al,P2 + out dx,ax + endm +; +cseg segment para public 'CODE' + assume cs:cseg, ds:dseg +start proc near + mov ax,dseg + mov ds,ax +; +; Select graphics mode. +; +ifdef MEDRES_VIDEO_MODE + mov ax,010h +else + mov ax,0eh +endif + int 10h +; +; ES always points to VGA memory. +; + mov ax,VIDEO_SEGMENT + mov es,ax +; +; Draw border around playfield in both pages. +; + mov di,PAGE0_OFFSET + call DrawBorder ;page 0 border + mov di,PAGE1_OFFSET + call DrawBorder ;page 1 border +; +; Draw all four plane's worth of the ball to undisplayed VGA memory. +; + mov al,01h ;enable plane 0 + SETREG SC_INDEX, MAP_MASK + mov si,offset BallPlane0Image + mov di,BALL_OFFSET + mov cx,BALL_WIDTH * BALL_HEIGHT + rep movsb + mov al,02h ;enable plane 1 + SETREG SC_INDEX, MAP_MASK + mov si,offset BallPlane1Image + mov di,BALL_OFFSET + mov cx,BALL_WIDTH * BALL_HEIGHT + rep movsb + mov al,04h ;enable plane 2 + SETREG SC_INDEX, MAP_MASK + mov si,offset BallPlane2Image + mov di,BALL_OFFSET + mov cx,BALL_WIDTH * BALL_HEIGHT + rep movsb + mov al,08h ;enable plane 3 + SETREG SC_INDEX, MAP_MASK + mov si,offset BallPlane3Image + mov di,BALL_OFFSET + mov cx,BALL_WIDTH * BALL_HEIGHT + rep movsb +; +; Draw a blank image the size of the ball to undisplayed VGA memory. +; + mov al,0fh ;enable all memory planes, since the + SETREG SC_INDEX, MAP_MASK ; blank has to erase all planes + mov di,BLANK_OFFSET + mov cx,BALL_WIDTH * BALL_HEIGHT + sub al,al + rep stosb +; +; Set VGA to write mode 1, for block copying ball and blank images. +; + mov dx,GC_INDEX + mov al,GC_MODE + out dx,al ;point GC Index to GC Mode register + inc dx ;point to GC Data register + jmp $+2 ;delay to let bus settle + in al,dx ;get current state of GC Mode + and al,not 3 ;clear the write mode bits + or al,1 ;set the write mode field to 1 + jmp $+2 ;delay to let bus settle + out dx,al +; +; Set VGA offset register in words to define logical screen width. +; + mov al,LOGICAL_SCREEN_WIDTH / 2 + SETREG CRTC_INDEX, CRTC_OFFSET +; +; Move the balls by erasing each ball, moving it, and +; redrawing it, then switching pages when they're all moved. +; +BallAnimationLoop: + mov bx,( NUM_BALLS * 2 ) - 2 +EachBallLoop: +; +; Erase old image of ball in this page (at location from one more earlier). +; + mov si,BLANK_OFFSET ;point to blank image + mov cx,[LastBallX+bx] + mov dx,[LastBallY+bx] + call DrawBall +; +; Set new last ball location. +; + mov ax,[BallX+bx] + mov [LastballX+bx],ax + mov ax,[BallY+bx] + mov [LastballY+bx],ax +; +; Change the ball movement values if it's time to do so. +; + dec [BallRep+bx] ;has current repeat factor run out? + jnz MoveBall + mov si,[BallControl+bx] ;it's time to change movement values + lodsw ;get new repeat factor from + ; control string + and ax,ax ;at end of control string? + jnz SetNewMove + mov si,[BallControlString+bx] ;reset control string + lodsw ;get new repeat factor +SetNewMove: + mov [BallRep+bx],ax ;set new movement repeat factor + lodsw ;set new x movement increment + mov [BallXInc+bx],ax + lodsw ;set new y movement increment + mov [BallYInc+bx],ax + mov [BallControl+bx],si ;save new control string pointer +; +; Move the ball. +; +MoveBall: + mov ax,[BallXInc+bx] + add [BallX+bx],ax ;move in x direction + mov ax,[BallYInc+bx] + add [BallY+bx],ax ;move in y direction +; +; Draw ball at new location. +; + mov si,BALL_OFFSET ;point to ball's image + mov cx,[BallX+bx] + mov dx,[BallY+bx] + call DrawBall +; + dec bx + dec bx + jns EachBallLoop - ; - ; Set up the next panning state (but don't program it into the - ; VGA yet). - ; - call AdjustPanning +; +; Set up the next panning state (but don't program it into the +; VGA yet). +; + call AdjustPanning - ; - ; Wait for display enable (pixel data being displayed) so we know - ; we're nowhere near vertical sync, where the start address gets - ; latched and used. - ; - call WaitDisplayEnable - ; - ; Flip to the new page by changing the start address. - ; - mov ax,[CurrentPageOffset] - add ax,[PanningStartOffset] - push ax - SETREG CRTC_INDEX, START_ADDRESS_LOW - mov al,byte ptr [CurrentPageOffset+1] - pop ax - mov al,ah - SETREG CRTC_INDEX, START_ADDRESS_HIGH - ; - ; Wait for vertical sync so the new start address has a chance - ; to take effect. - ; - call WaitVSync - ; - ; Set horizontal panning now, just as new start address takes effect. - ; - mov al,[HPan] - mov dx,INPUT_STATUS_1 - in al,dx ;reset AC addressing to index reg - mov dx,AC_INDEX - mov al,HPELPAN - out dx,al ;set AC index to pel pan reg - mov al,[HPan] - out dx,al ;set new pel panning - ; - ; Flip the page to draw to to the undisplayed page. - ; - xor [CurrentPage],1 - jnz IsPage1 - mov [CurrentPageOffset],PAGE0_OFFSET - jmp short EndFlipPage - IsPage1: - mov [CurrentPageOffset],PAGE1_OFFSET - EndFlipPage: - ; - ; Exit if a key's been hit. - ; - mov ah,1 - int 16h - jnz Done - jmp BallAnimationLoop - ; - ; Finished, clear key, reset screen mode and exit. - ; - Done: - mov ah,0 ;clear key - int 16h - ; - mov ax,3 ;reset to text mode - int 10h - ; - mov ah,4ch ;exit to DOS - int 21h - ; - start endp - ; - ; Routine to draw a ball-sized image to all planes, copying from - ; offset SI in VGA memory to offset CX,DX (x,y) in VGA memory in - ; the current page. - ; - DrawBall proc near - mov ax,LOGICAL_SCREEN_WIDTH - mul dx ;offset of start of top image scan line - add ax,cx ;offset of upper left of image - add ax,[CurrentPageOffset] ;offset of start of page - mov di,ax - mov bp,BALL_HEIGHT - push ds - push es - pop ds ;move from VGA memory to VGA memory - DrawBallLoop: - push di - mov cx,BALL_WIDTH - rep movsb ;draw a scan line of image - pop di - add di,LOGICAL_SCREEN_WIDTH ;point to next destination scan line - dec bp - jnz DrawBallLoop - pop ds - ret - DrawBall endp - ; - ; Wait for the leading edge of vertical sync pulse. - ; - WaitVSync proc near - mov dx,INPUT_STATUS_1 - WaitNotVSyncLoop: - in al,dx - and al,VSYNC_MASK - jnz WaitNotVSyncLoop - WaitVSyncLoop: - in al,dx - and al,VSYNC_MASK - jz WaitVSyncLoop - ret - WaitVSync endp +; +; Wait for display enable (pixel data being displayed) so we know +; we're nowhere near vertical sync, where the start address gets +; latched and used. +; + call WaitDisplayEnable +; +; Flip to the new page by changing the start address. +; + mov ax,[CurrentPageOffset] + add ax,[PanningStartOffset] + push ax + SETREG CRTC_INDEX, START_ADDRESS_LOW + mov al,byte ptr [CurrentPageOffset+1] + pop ax + mov al,ah + SETREG CRTC_INDEX, START_ADDRESS_HIGH +; +; Wait for vertical sync so the new start address has a chance +; to take effect. +; + call WaitVSync +; +; Set horizontal panning now, just as new start address takes effect. +; + mov al,[HPan] + mov dx,INPUT_STATUS_1 + in al,dx ;reset AC addressing to index reg + mov dx,AC_INDEX + mov al,HPELPAN + out dx,al ;set AC index to pel pan reg + mov al,[HPan] + out dx,al ;set new pel panning +; +; Flip the page to draw to to the undisplayed page. +; + xor [CurrentPage],1 + jnz IsPage1 + mov [CurrentPageOffset],PAGE0_OFFSET + jmp short EndFlipPage +IsPage1: + mov [CurrentPageOffset],PAGE1_OFFSET +EndFlipPage: +; +; Exit if a key's been hit. +; + mov ah,1 + int 16h + jnz Done + jmp BallAnimationLoop +; +; Finished, clear key, reset screen mode and exit. +; +Done: + mov ah,0 ;clear key + int 16h +; + mov ax,3 ;reset to text mode + int 10h +; + mov ah,4ch ;exit to DOS + int 21h +; +start endp +; +; Routine to draw a ball-sized image to all planes, copying from +; offset SI in VGA memory to offset CX,DX (x,y) in VGA memory in +; the current page. +; +DrawBall proc near + mov ax,LOGICAL_SCREEN_WIDTH + mul dx ;offset of start of top image scan line + add ax,cx ;offset of upper left of image + add ax,[CurrentPageOffset] ;offset of start of page + mov di,ax + mov bp,BALL_HEIGHT + push ds + push es + pop ds ;move from VGA memory to VGA memory +DrawBallLoop: + push di + mov cx,BALL_WIDTH + rep movsb ;draw a scan line of image + pop di + add di,LOGICAL_SCREEN_WIDTH ;point to next destination scan line + dec bp + jnz DrawBallLoop + pop ds + ret +DrawBall endp +; +; Wait for the leading edge of vertical sync pulse. +; +WaitVSync proc near + mov dx,INPUT_STATUS_1 +WaitNotVSyncLoop: + in al,dx + and al,VSYNC_MASK + jnz WaitNotVSyncLoop +WaitVSyncLoop: + in al,dx + and al,VSYNC_MASK + jz WaitVSyncLoop + ret +WaitVSync endp - ; - ; Wait for display enable to happen (pixels to be scanned to - ; the screen, indicating we're in the middle of displaying a frame). - ; - WaitDisplayEnable proc near - mov dx,INPUT_STATUS_1 - WaitDELoop: - in al,dx - and al,DE_MASK - jnz WaitDELoop - ret - WaitDisplayEnable endp +; +; Wait for display enable to happen (pixels to be scanned to +; the screen, indicating we're in the middle of displaying a frame). +; +WaitDisplayEnable proc near + mov dx,INPUT_STATUS_1 +WaitDELoop: + in al,dx + and al,DE_MASK + jnz WaitDELoop + ret +WaitDisplayEnable endp - ; - ; Perform horizontal/vertical panning. - ; - AdjustPanning proc near - dec [PanningRep] ;time to get new panning values? - jnz DoPan - mov si,[PanningControl] ;point to current location in - ; panning control string - lodsw ;get panning repeat factor - and ax,ax ;at end of panning control string? - jnz SetnewPanValues - mov si,offset PanningControlString ;reset to start of string - lodsw ;get panning repeat factor - SetNewPanValues: - mov [PanningRep],ax ;set new panning repeat value - lodsw - mov [PanningXInc],ax ;horizontal panning value - lodsw - mov [PanningYInc],ax ;vertical panning value - mov [PanningControl],si ;save current location in panning - ; control string - ; - ; Pan according to panning values. - ; - DoPan: - mov ax,[PanningXInc] ;horizontal panning - and ax,ax - js PanLeft ;negative means pan left - jz CheckVerticalPan - mov al,[HPan] - inc al ;pan right; if pel pan reaches - cmp al,8 ; 8, it's time to move to the - jb SetHPan ; next byte with a pel pan of 0 - sub al,al ; and a start offset that's one - inc [PanningStartOffset] ; higher - jmp short SetHPan - PanLeft: - mov al,[HPan] - dec al ;pan left; if pel pan reaches -1, - jns SetHPan ; it's time to move to the next - mov al,7 ; byte with a pel pan of 7 and a - dec [PanningStartOffset] ; start offset that's one lower - SetHPan: - mov [HPan],al ;save new pel pan value - CheckVerticalPan: - mov ax,[PanningYInc] ;vertical panning - and ax,ax - js PanUp ;negative means pan up - jz EndPan - add [PanningStartOffset],LOGICAL_SCREEN_WIDTH - ;pan down by advancing the start - ; address by a scan line - jmp short EndPan - PanUp: - sub [PanningStartOffset],LOGICAL_SCREEN_WIDTH - ;pan up by retarding the start - ; address by a scan line - EndPan: - ret - ; - ; Draw textured border around playfield that starts at DI. - ; - DrawBorder proc near - ; - ; Draw the left border. - ; - push di - mov cx,LOGICAL_SCREEN_HEIGHT / 16 - DrawLeftBorderLoop: - mov al,0ch ;select red color for block - call DrawBorderBlock - add di,LOGICAL_SCREEN_WIDTH * 8 - mov al,0eh ;select yellow color for block - call DrawBorderBlock - add di,LOGICAL_SCREEN_WIDTH * 8 - loop DrawLeftBorderLoop - pop di - ; - ; Draw the right border. - ; - push di - add di,LOGICAL_SCREEN_WIDTH - 1 - mov cx,LOGICAL_SCREEN_HEIGHT / 16 - DrawRightBorderLoop: - mov al,0eh ;select yellow color for block - call DrawBorderBlock - add di,LOGICAL_SCREEN_WIDTH * 8 - mov al,0ch ;select red color for block - call DrawBorderBlock - add di,LOGICAL_SCREEN_WIDTH * 8 - loop DrawRightBorderLoop - pop di - ; - ; Draw the top border. - ; - push di - mov cx,(LOGICAL_SCREEN_WIDTH - 2) / 2 - DrawTopBorderLoop: - inc di - mov al,0eh ;select yellow color for block - call DrawBorderBlock - inc di - mov al,0ch ;select red color for block - call DrawBorderBlock - loop DrawTopBorderLoop - pop di - ; - ; Draw the bottom border. - ; - add di,(LOGICAL_SCREEN_HEIGHT - 8) * LOGICAL_SCREEN_WIDTH - mov cx,(LOGICAL_SCREEN_WIDTH - 2) / 2 - DrawBottomBorderLoop: - inc di - mov al,0ch ;select red color for block - call DrawBorderBlock - inc di - mov al,0eh ;select yellow color for block - call DrawBorderBlock - loop DrawBottomBorderLoop - ret - DrawBorder endp - ; - ; Draws an 8x8 border block in color in AL at location DI. - ; DI preserved. - ; - DrawBorderBlock proc near - push di - SETREG SC_INDEX, MAP_MASK - mov al,0ffh - rept 8 - stosb - add di,LOGICAL_SCREEN_WIDTH - 1 - endm - pop di - ret - DrawBorderBlock endp - AdjustPanning endp - cseg ends - end start +; +; Perform horizontal/vertical panning. +; +AdjustPanning proc near + dec [PanningRep] ;time to get new panning values? + jnz DoPan + mov si,[PanningControl] ;point to current location in + ; panning control string + lodsw ;get panning repeat factor + and ax,ax ;at end of panning control string? + jnz SetnewPanValues + mov si,offset PanningControlString ;reset to start of string + lodsw ;get panning repeat factor +SetNewPanValues: + mov [PanningRep],ax ;set new panning repeat value + lodsw + mov [PanningXInc],ax ;horizontal panning value + lodsw + mov [PanningYInc],ax ;vertical panning value + mov [PanningControl],si ;save current location in panning + ; control string +; +; Pan according to panning values. +; +DoPan: + mov ax,[PanningXInc] ;horizontal panning + and ax,ax + js PanLeft ;negative means pan left + jz CheckVerticalPan + mov al,[HPan] + inc al ;pan right; if pel pan reaches + cmp al,8 ; 8, it's time to move to the + jb SetHPan ; next byte with a pel pan of 0 + sub al,al ; and a start offset that's one + inc [PanningStartOffset] ; higher + jmp short SetHPan +PanLeft: + mov al,[HPan] + dec al ;pan left; if pel pan reaches -1, + jns SetHPan ; it's time to move to the next + mov al,7 ; byte with a pel pan of 7 and a + dec [PanningStartOffset] ; start offset that's one lower +SetHPan: + mov [HPan],al ;save new pel pan value +CheckVerticalPan: + mov ax,[PanningYInc] ;vertical panning + and ax,ax + js PanUp ;negative means pan up + jz EndPan + add [PanningStartOffset],LOGICAL_SCREEN_WIDTH + ;pan down by advancing the start + ; address by a scan line + jmp short EndPan +PanUp: + sub [PanningStartOffset],LOGICAL_SCREEN_WIDTH + ;pan up by retarding the start + ; address by a scan line +EndPan: + ret +; +; Draw textured border around playfield that starts at DI. +; +DrawBorder proc near +; +; Draw the left border. +; + push di + mov cx,LOGICAL_SCREEN_HEIGHT / 16 +DrawLeftBorderLoop: + mov al,0ch ;select red color for block + call DrawBorderBlock + add di,LOGICAL_SCREEN_WIDTH * 8 + mov al,0eh ;select yellow color for block + call DrawBorderBlock + add di,LOGICAL_SCREEN_WIDTH * 8 + loop DrawLeftBorderLoop + pop di +; +; Draw the right border. +; + push di + add di,LOGICAL_SCREEN_WIDTH - 1 + mov cx,LOGICAL_SCREEN_HEIGHT / 16 +DrawRightBorderLoop: + mov al,0eh ;select yellow color for block + call DrawBorderBlock + add di,LOGICAL_SCREEN_WIDTH * 8 + mov al,0ch ;select red color for block + call DrawBorderBlock + add di,LOGICAL_SCREEN_WIDTH * 8 + loop DrawRightBorderLoop + pop di +; +; Draw the top border. +; + push di + mov cx,(LOGICAL_SCREEN_WIDTH - 2) / 2 +DrawTopBorderLoop: + inc di + mov al,0eh ;select yellow color for block + call DrawBorderBlock + inc di + mov al,0ch ;select red color for block + call DrawBorderBlock + loop DrawTopBorderLoop + pop di +; +; Draw the bottom border. +; + add di,(LOGICAL_SCREEN_HEIGHT - 8) * LOGICAL_SCREEN_WIDTH + mov cx,(LOGICAL_SCREEN_WIDTH - 2) / 2 +DrawBottomBorderLoop: + inc di + mov al,0ch ;select red color for block + call DrawBorderBlock + inc di + mov al,0eh ;select yellow color for block + call DrawBorderBlock + loop DrawBottomBorderLoop + ret +DrawBorder endp +; +; Draws an 8x8 border block in color in AL at location DI. +; DI preserved. +; +DrawBorderBlock proc near + push di + SETREG SC_INDEX, MAP_MASK + mov al,0ffh + rept 8 + stosb + add di,LOGICAL_SCREEN_WIDTH - 1 + endm + pop di + ret +DrawBorderBlock endp +AdjustPanning endp +cseg ends + end start +``` diff --git a/24-02.md b/24-02.md index 1e4e60d..4870904 100644 --- a/24-02.md +++ b/24-02.md @@ -12,254 +12,256 @@ pages: 453-458 **LISTING 24.1 L24-1.ASM** - ; 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' - db 512 dup(?) - stack ends - ; - VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment - SCREEN_HEIGHT equ 350 - SCREEN_WIDTH_IN_BYTES equ 80 - DEMO_AREA_HEIGHT equ 336 ;# of scan lines in area - ; logical function operation - ; is demonstrated in - DEMO_AREA_WIDTH_IN_BYTES equ 40 ;width in bytes of area - ; logical function operation - ; is demonstrated in - VERTICAL_BOX_WIDTH_IN_BYTES equ 10 ;width in bytes of the box used to - ; demonstrate each logical function - ; - ; VGA register equates. - ; - GC_INDEX equ 3ceh ;GC index register - GC_ROTATE equ 3 ;GC data rotate/logical function - ; register index - GC_MODE equ 5 ;GC mode register index - ; - dseg segment para common ‘DATA' - ; - ; String used to label logical functions. - ; - LabelString label byte - db ‘UNMODIFIED AND OR XOR ' - LABEL_STRING_LENGTH equ $-LabelString - ; - ; Strings used to label fill patterns. - ; - FillPatternFF db ‘Fill Pattern: 0FFh' - FILL_PATTERN_FF_LENGTH equ $ - FillPatternFF - FillPattern00 db ‘Fill Pattern: 000h' - FILL_PATTERN_00_LENGTH equ $ - FillPattern00 - FillPatternVert db ‘Fill Pattern: Vertical Bar' - FILL_PATTERN_VERT_LENGTH equ $ - FillPatternVert - FillPatternHorz db ‘Fill Pattern: Horizontal Bar' - FILL_PATTERN_HORZ_LENGTH equ $ - FillPatternHorz - ; - dseg ends - ; - ; Macro to set indexed register INDEX of GC chip to SETTING. - ; - SETGC macro INDEX, SETTING - mov dx,GC_INDEX - mov ax,(SETTING SHL 8) OR INDEX - out dx,ax - endm - ; - ; - ; Macro to call BIOS write string function to display text string - ; TEXT_STRING, of length TEXT_LENGTH, at location ROW,COLUMN. - ; - TEXT_UP macro TEXT_STRING, TEXT_LENGTH, ROW, COLUMN - mov ah,13h ;BIOS write string function - mov bp,offset TEXT_STRING ;ES:BP points to string - mov cx,TEXT_LENGTH - mov dx,(ROW SHL 8) OR COLUMN ;position - sub al,al ;string is chars only, cursor not moved - mov bl,7 ;text attribute is white (light gray) - int 10h - endm - ; - cseg segment para public ‘CODE' - assume cs:cseg, ds:dseg - start proc near - mov ax,dseg - mov ds,ax - ; - ; Select 640x350 graphics mode. - ; - mov ax,010h - int 10h - ; - ; ES points to VGA memory. - ; - mov ax,VGA_VIDEO_SEGMENT - mov es,ax - ; - ; Draw background of horizontal bars. - ; - mov dx,SCREEN_HEIGHT/4 - ;# of bars to draw (each 4 pixels high) - sub di,di ;start at offset 0 in display memory - mov ax,0ffffh ;fill pattern for light areas of bars - mov bx,DEMO_AREA_WIDTH_IN_BYTES / 2 ;length of each bar - mov si,SCREEN_WIDTH_IN_BYTES - DEMO_AREA_WIDTH_IN_BYTES - mov bp,(SCREEN_WIDTH_IN_BYTES * 3) - DEMO_AREA_WIDTH_IN_BYTES - BackgroundLoop: - mov cx,bx ;length of bar - rep stosw ;draw top half of bar - add di,si ;point to start of bottom half of bar - mov cx,bx ;length of bar - rep stosw ;draw bottom half of bar - add di,bp ;point to start of top of next bar - dec dx - jnz BackgroundLoop - ; - ; Draw vertical boxes filled with a variety of fill patterns - ; using each of the 4 logical functions in turn. - ; - SETGC GC_ROTATE, 0 ;select data unmodified - ; logical function... - mov di,0 - call DrawVerticalBox ;...and draw box - ; - SETGC GC_ROTATE, 08h ;select AND logical function... - mov di,10 - call DrawVerticalBox ;...and draw box - ; - SETGC GC_ROTATE, 10h ;select OR logical function... - mov di,20 - call DrawVerticalBox ;...and draw box - ; - SETGC GC_ROTATE, 18h ;select XOR logical function... - mov di,30 - call DrawVerticalBox ;...and draw box - ; - ; Reset the logical function to data unmodified, the default state. - ; - SETGC GC_ROTATE, 0 - ; - ; Label the screen. - ; - push ds - 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 - ; write string function. - ; - TEXT_UP LabelString, LABEL_STRING_LENGTH, 24, 0 - ; - ; Label the fill patterns, using the VGA BIOS's - ; write string function. - ; - TEXT_UP FillPatternFF, FILL_PATTERN_FF_LENGTH, 3, 42 - TEXT_UP FillPattern00, FILL_PATTERN_00_LENGTH, 9, 42 - 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. - ; - WaitForKey: - mov ah,1 - int 16h - jz WaitForKey - ; - ; Finished. Clear key, reset screen mode and exit. - ; - Done: - mov ah,0 ;clear key that we just detected - int 16h - ; - mov ax,3 ;reset to text mode - int 10h - ; - mov ah,4ch ;exit to DOS - int 21h - ; - start endp - ; - ; Subroutine to draw a box 80x336 in size, using currently selected - ; logical function, with upper left corner at the display memory offset - ; in DI. Box is filled with four patterns. Top quarter of area is - ; filled with 0FFh (solid) pattern, next quarter is filled with 00h - ; (empty) pattern, next quarter is filled with 33h (double pixel wide - ; vertical bar) pattern, and bottom quarter is filled with double pixel - ; high horizontal bar pattern. - ; - ; Macro to draw a column of the specified width in bytes, one-quarter - ; of the height of the box, with the specified fill pattern. - ; - DRAW_BOX_QUARTER macro FILL, WIDTH - local RowLoop, ColumnLoop - mov al,FILL ;fill pattern - mov dx,DEMO_AREA_HEIGHT / 4 ;1/4 of the full box height - RowLoop: - mov cx,WIDTH - ColumnLoop: - mov ah,es:[di] ;load display memory contents into - ; GC latches (we don't actually care - ; about value read into AH) - stosb ;write pattern, which is logically - ; combined with latch contents for each - ; plane and then written to display - ; memory - loop ColumnLoop - add di,SCREEN_WIDTH_IN_BYTES - WIDTH - ;point to start of next line down in box - dec dx - jnz RowLoop - endm - ; - DrawVerticalBox proc near - DRAW_BOX_QUARTER 0ffh, VERTICAL_BOX_WIDTH_IN_BYTES - ;first fill pattern: solid fill - DRAW_BOX_QUARTER 0, VERTICAL_BOX_WIDTH_IN_BYTES - ;second fill pattern: empty fill - DRAW_BOX_QUARTER 033h, VERTICAL_BOX_WIDTH_IN_BYTES - ;third fill pattern: double-pixel - ; wide vertical bars - mov dx,DEMO_AREA_HEIGHT / 4 / 4 - ;fourth fill pattern: horizontal bars in - ; sets of 4 scan lines - sub ax,ax - mov si,VERTICAL_BOX_WIDTH_IN_BYTES ;width of fill area - HorzBarLoop: - 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) - stosb ;write solid pattern, through ALUs - loop HBLoop1 - add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES - mov cx,si ;width to fill - HBLoop2: - mov bl,es:[di] ;load latches - stosb ;write solid pattern, through ALUs - loop HBLoop2 - add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES - inc ax ;0 fill (smaller to do word than byte DEC) - mov cx,si ;width to fill - HBLoop3: - mov bl,es:[di] ;load latches - stosb ;write empty pattern, through ALUs - loop HBLoop3 - add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES - mov cx,si ;width to fill - HBLoop4: - mov bl,es:[di] ;load latches - stosb ;write empty pattern, through ALUs - loop HBLoop4 - add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES - dec dx - jnz HorzBarLoop - ; - ret - DrawVerticalBox endp - cseg ends - end start +```nasm +; 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' + db 512 dup(?) +stack ends +; +VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment +SCREEN_HEIGHT equ 350 +SCREEN_WIDTH_IN_BYTES equ 80 +DEMO_AREA_HEIGHT equ 336 ;# of scan lines in area + ; logical function operation + ; is demonstrated in +DEMO_AREA_WIDTH_IN_BYTES equ 40 ;width in bytes of area + ; logical function operation + ; is demonstrated in +VERTICAL_BOX_WIDTH_IN_BYTES equ 10 ;width in bytes of the box used to + ; demonstrate each logical function +; +; VGA register equates. +; +GC_INDEX equ 3ceh ;GC index register +GC_ROTATE equ 3 ;GC data rotate/logical function + ; register index +GC_MODE equ 5 ;GC mode register index +; +dseg segment para common ‘DATA' +; +; String used to label logical functions. +; +LabelString label byte + db ‘UNMODIFIED AND OR XOR ' +LABEL_STRING_LENGTH equ $-LabelString +; +; Strings used to label fill patterns. +; +FillPatternFF db ‘Fill Pattern: 0FFh' +FILL_PATTERN_FF_LENGTH equ $ - FillPatternFF +FillPattern00 db ‘Fill Pattern: 000h' +FILL_PATTERN_00_LENGTH equ $ - FillPattern00 +FillPatternVert db ‘Fill Pattern: Vertical Bar' +FILL_PATTERN_VERT_LENGTH equ $ - FillPatternVert +FillPatternHorz db ‘Fill Pattern: Horizontal Bar' +FILL_PATTERN_HORZ_LENGTH equ $ - FillPatternHorz +; +dseg ends +; +; Macro to set indexed register INDEX of GC chip to SETTING. +; +SETGC macro INDEX, SETTING + mov dx,GC_INDEX + mov ax,(SETTING SHL 8) OR INDEX + out dx,ax + endm +; +; +; Macro to call BIOS write string function to display text string +; TEXT_STRING, of length TEXT_LENGTH, at location ROW,COLUMN. +; +TEXT_UP macro TEXT_STRING, TEXT_LENGTH, ROW, COLUMN + mov ah,13h ;BIOS write string function + mov bp,offset TEXT_STRING ;ES:BP points to string + mov cx,TEXT_LENGTH + mov dx,(ROW SHL 8) OR COLUMN ;position + sub al,al ;string is chars only, cursor not moved + mov bl,7 ;text attribute is white (light gray) + int 10h + endm +; +cseg segment para public ‘CODE' + assume cs:cseg, ds:dseg +start proc near + mov ax,dseg + mov ds,ax +; +; Select 640x350 graphics mode. +; + mov ax,010h + int 10h +; +; ES points to VGA memory. +; + mov ax,VGA_VIDEO_SEGMENT + mov es,ax +; +; Draw background of horizontal bars. +; + mov dx,SCREEN_HEIGHT/4 + ;# of bars to draw (each 4 pixels high) + sub di,di ;start at offset 0 in display memory + mov ax,0ffffh ;fill pattern for light areas of bars + mov bx,DEMO_AREA_WIDTH_IN_BYTES / 2 ;length of each bar + mov si,SCREEN_WIDTH_IN_BYTES - DEMO_AREA_WIDTH_IN_BYTES + mov bp,(SCREEN_WIDTH_IN_BYTES * 3) - DEMO_AREA_WIDTH_IN_BYTES +BackgroundLoop: + mov cx,bx ;length of bar + rep stosw ;draw top half of bar + add di,si ;point to start of bottom half of bar + mov cx,bx ;length of bar + rep stosw ;draw bottom half of bar + add di,bp ;point to start of top of next bar + dec dx + jnz BackgroundLoop +; +; Draw vertical boxes filled with a variety of fill patterns +; using each of the 4 logical functions in turn. +; + SETGC GC_ROTATE, 0 ;select data unmodified + ; logical function... + mov di,0 + call DrawVerticalBox ;...and draw box +; + SETGC GC_ROTATE, 08h ;select AND logical function... + mov di,10 + call DrawVerticalBox ;...and draw box +; + SETGC GC_ROTATE, 10h ;select OR logical function... + mov di,20 + call DrawVerticalBox ;...and draw box +; + SETGC GC_ROTATE, 18h ;select XOR logical function... + mov di,30 + call DrawVerticalBox ;...and draw box +; +; Reset the logical function to data unmodified, the default state. +; + SETGC GC_ROTATE, 0 +; +; Label the screen. +; + push ds + 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 +; write string function. +; + TEXT_UP LabelString, LABEL_STRING_LENGTH, 24, 0 +; +; Label the fill patterns, using the VGA BIOS's +; write string function. +; + TEXT_UP FillPatternFF, FILL_PATTERN_FF_LENGTH, 3, 42 + TEXT_UP FillPattern00, FILL_PATTERN_00_LENGTH, 9, 42 + 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. +; +WaitForKey: + mov ah,1 + int 16h + jz WaitForKey +; +; Finished. Clear key, reset screen mode and exit. +; +Done: + mov ah,0 ;clear key that we just detected + int 16h +; + mov ax,3 ;reset to text mode + int 10h +; + mov ah,4ch ;exit to DOS + int 21h +; +start endp +; +; Subroutine to draw a box 80x336 in size, using currently selected +; logical function, with upper left corner at the display memory offset +; in DI. Box is filled with four patterns. Top quarter of area is +; filled with 0FFh (solid) pattern, next quarter is filled with 00h +; (empty) pattern, next quarter is filled with 33h (double pixel wide +; vertical bar) pattern, and bottom quarter is filled with double pixel +; high horizontal bar pattern. +; +; Macro to draw a column of the specified width in bytes, one-quarter +; of the height of the box, with the specified fill pattern. +; +DRAW_BOX_QUARTER macro FILL, WIDTH + local RowLoop, ColumnLoop + mov al,FILL ;fill pattern + mov dx,DEMO_AREA_HEIGHT / 4 ;1/4 of the full box height +RowLoop: + mov cx,WIDTH +ColumnLoop: + mov ah,es:[di] ;load display memory contents into + ; GC latches (we don't actually care + ; about value read into AH) + stosb ;write pattern, which is logically + ; combined with latch contents for each + ; plane and then written to display + ; memory + loop ColumnLoop + add di,SCREEN_WIDTH_IN_BYTES - WIDTH + ;point to start of next line down in box + dec dx + jnz RowLoop + endm +; +DrawVerticalBox proc near + DRAW_BOX_QUARTER 0ffh, VERTICAL_BOX_WIDTH_IN_BYTES + ;first fill pattern: solid fill + DRAW_BOX_QUARTER 0, VERTICAL_BOX_WIDTH_IN_BYTES + ;second fill pattern: empty fill + DRAW_BOX_QUARTER 033h, VERTICAL_BOX_WIDTH_IN_BYTES + ;third fill pattern: double-pixel + ; wide vertical bars + mov dx,DEMO_AREA_HEIGHT / 4 / 4 + ;fourth fill pattern: horizontal bars in + ; sets of 4 scan lines + sub ax,ax + mov si,VERTICAL_BOX_WIDTH_IN_BYTES ;width of fill area +HorzBarLoop: + 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) + stosb ;write solid pattern, through ALUs + loop HBLoop1 + add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES + mov cx,si ;width to fill +HBLoop2: + mov bl,es:[di] ;load latches + stosb ;write solid pattern, through ALUs + loop HBLoop2 + add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES + inc ax ;0 fill (smaller to do word than byte DEC) + mov cx,si ;width to fill +HBLoop3: + mov bl,es:[di] ;load latches + stosb ;write empty pattern, through ALUs + loop HBLoop3 + add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES + mov cx,si ;width to fill +HBLoop4: + mov bl,es:[di] ;load latches + stosb ;write empty pattern, through ALUs + loop HBLoop4 + add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES + dec dx + jnz HorzBarLoop +; + ret +DrawVerticalBox endp +cseg ends + end start +``` diff --git a/24-03.md b/24-03.md index 9a7dcbb..5bdba8c 100644 --- a/24-03.md +++ b/24-03.md @@ -90,16 +90,19 @@ 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. The obvious way to implement this would be with - MOV DL,VALUE1 - MOV DH,VALUE2 - +```nasm +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, **SHL** (\*100H would work also), and then logically combining the values with MASM's **OR** operator (or the **ADD** operator), both halves of DX can be loaded with a single instruction, as in - MOV DX,(VALUE2 SHL 8) OR VALUE1 +```nasm +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 diff --git a/25-02.md b/25-02.md index a3f413f..480c106 100644 --- a/25-02.md +++ b/25-02.md @@ -12,236 +12,238 @@ pages: 466-470 **LISTING 25.1 L25-1.ASM** - ; 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 - ; for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h. - ; By Michael Abrash. - ; - stack segment para stack ‘STACK' - db 512 dup(?) - stack ends - ; - VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment - SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable - FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char - ; - ; VGA register equates. - ; - GC_INDEX equ 3ceh ;GC index register - GC_ROTATE equ 3 ;GC data rotate/logical function - ; register index - GC_BIT_MASK equ 8 ;GC bit mask register index - ; - 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 +```nasm +; 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 +; for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h. +; By Michael Abrash. +; +stack segment para stack ‘STACK' + db 512 dup(?) +stack ends +; +VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment +SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable +FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char +; +; VGA register equates. +; +GC_INDEX equ 3ceh ;GC index register +GC_ROTATE equ 3 ;GC data rotate/logical function + ; register index +GC_BIT_MASK equ 8 ;GC bit mask register index +; +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. - FontPointer dd ? ;font offset - dseg ends - ; - ; Macro to set indexed register INDEX of GC chip to SETTING. - ; - SETGC macro INDEX, SETTING - mov dx,GC_INDEX - mov ax,(SETTING SHL 8) OR INDEX - out dx,ax - endm - ; - cseg segment para public ‘CODE' - assume cs:cseg, ds:dseg - start proc near - mov ax,dseg - mov ds,ax - ; - ; Select 640x480 graphics mode. - ; - mov ax,012h - int 10h - ; - ; Set driver to use the 8x8 font. - ; - mov ah,11h ;VGA BIOS character generator function, - mov al,30h ; return info subfunction - mov bh,3;get 8x8 font pointer - int 10h - call SelectFont - ; - ; Print the test string. - ; - mov si,offset TestString - mov bx,TEST_TEXT_ROW - mov cx,TEST_TEXT_COL - StringOutLoop: - lodsb - and al,al - jz StringOutDone - call DrawChar - add cx,TEST_TEXT_WIDTH - jmp StringOutLoop - StringOutDone: - ; - ; Reset the data rotate and bit mask registers. - ; - SETGC GC_ROTATE, 0 - SETGC GC_BIT_MASK, 0ffh - ; - ; Wait for a keystroke. - ; - mov ah,1 - int 21h - ; - ; Return to text mode. - ; - mov ax,03h - int 10h - ; - ; Exit to DOS. - ; - mov ah,4ch - int 21h - Start endp - ; - ; Subroutine to draw a text character in a linear graphics mode - ; (0Dh, 0Eh, 0Fh, 010h, 012h). - ; Font used should be pointed to by FontPointer. - ; - ; Input: - ; AL = character to draw - ; BX = row to draw text character at - ; CX = column to draw text character at - ; - ; Forces ALU function to "move". - ; - DrawChar proc near - push ax - push bx - push cx - push dx - push si - push di - push bp - push ds - ; - ; Set DS:SI to point to font and ES to point to display memory. - ; - lds si,[FontPointer] ;point to font - mov dx,VGA_VIDEO_SEGMENT - mov es,dx ;point to display memory - ; - ; Calculate screen address of byte character starts in. - ; - push ds ;point to BIOS data segment - sub dx,dx - mov ds,dx - xchg ax,bx - mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS - ; screen width - pop ds - mul di ;calculate offset of start of row - push di ;set aside screen width - mov di,cx ;set aside the column - and cl,0111b ;keep only the column in-byte address - shr di,1 - shr di,1 - shr di,1 ;divide column by 8 to make a byte address - add di,ax ;and point to byte - ; - ; Calculate font address of character. - ; - sub bh,bh - shl bx,1 ;assumes 8 bytes per character; use - shl bx,1 ; a multiply otherwise - shl bx,1 ;offset in font of character - add si,bx ;offset in font segment of character - ; - ; Set up the GC rotation. - ; - mov dx,GC_INDEX - mov al,GC_ROTATE - mov ah,cl - out dx,ax - ; - ; Set up BH as bit mask for left half, - ; BL as rotation for right half. - ; - mov bx,0ffffh - shr bh,cl - neg cl - add cl,8 - shl bl,cl - ; - ; Draw the character, left half first, then right half in the - ; succeeding byte, using the data rotation to position the character - ; across the byte boundary and then using the bit mask to get the - ; proper portion of the character into each byte. - ; Does not check for case where character is byte-aligned and - ; no rotation and only one write is required. - ; - mov bp,FONT_CHARACTER_SIZE - mov dx,GC_INDEX - pop cx ;get back screen width - dec cx - dec cx ; -2 because do two bytes for each char - CharacterLoop: - ; - ; Set the bit mask for the left half of the character. - ; - mov al,GC_BIT_MASK - mov ah,bh - out dx,ax - ; - ; Get the next character byte & write it to display memory. - ; (Left half of character.) - ; - mov al,[si] ;get character byte - mov ah,es:[di] ;load latches - stosb ;write character byte - ; - ; Set the bit mask for the right half of the character. - ; - mov al,GC_BIT_MASK - mov ah,bl - out dx,ax - ; - ; Get the character byte again & write it to display memory. - ; (Right half of character.) - ; - lodsb ;get character byte - mov ah,es:[di] ;load latches - stosb ;write character byte - ; - ; Point to next line of character in display memory. - ; - add di,cx - ; - dec bp - jnz CharacterLoop - ; - pop ds - pop bp - pop di - pop si - pop dx - pop cx - pop bx - pop ax - ret - DrawChar endp - ; - ; Set the pointer to the font to draw from to ES:BP. - ; - SelectFont proc near - mov word ptr [FontPointer],bp ;save pointer - mov word ptr [FontPointer+2],es - ret - SelectFont endp - ; - cseg ends - end start +TestString label byte + db ‘Hello, world!',0 ;test string to print. +FontPointer dd ? ;font offset +dseg ends +; +; Macro to set indexed register INDEX of GC chip to SETTING. +; +SETGC macro INDEX, SETTING + mov dx,GC_INDEX + mov ax,(SETTING SHL 8) OR INDEX + out dx,ax + endm +; +cseg segment para public ‘CODE' + assume cs:cseg, ds:dseg +start proc near + mov ax,dseg + mov ds,ax +; +; Select 640x480 graphics mode. +; + mov ax,012h + int 10h +; +; Set driver to use the 8x8 font. +; + mov ah,11h ;VGA BIOS character generator function, + mov al,30h ; return info subfunction + mov bh,3;get 8x8 font pointer + int 10h + call SelectFont +; +; Print the test string. +; + mov si,offset TestString + mov bx,TEST_TEXT_ROW + mov cx,TEST_TEXT_COL +StringOutLoop: + lodsb + and al,al + jz StringOutDone + call DrawChar + add cx,TEST_TEXT_WIDTH + jmp StringOutLoop +StringOutDone: +; +; Reset the data rotate and bit mask registers. +; + SETGC GC_ROTATE, 0 + SETGC GC_BIT_MASK, 0ffh +; +; Wait for a keystroke. +; + mov ah,1 + int 21h +; +; Return to text mode. +; + mov ax,03h + int 10h +; +; Exit to DOS. +; + mov ah,4ch + int 21h +Start endp +; +; Subroutine to draw a text character in a linear graphics mode +; (0Dh, 0Eh, 0Fh, 010h, 012h). +; Font used should be pointed to by FontPointer. +; +; Input: +; AL = character to draw +; BX = row to draw text character at +; CX = column to draw text character at +; +; Forces ALU function to "move". +; +DrawChar proc near + push ax + push bx + push cx + push dx + push si + push di + push bp + push ds +; +; Set DS:SI to point to font and ES to point to display memory. +; + lds si,[FontPointer] ;point to font + mov dx,VGA_VIDEO_SEGMENT + mov es,dx ;point to display memory +; +; Calculate screen address of byte character starts in. +; + push ds ;point to BIOS data segment + sub dx,dx + mov ds,dx + xchg ax,bx + mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS + ; screen width + pop ds + mul di ;calculate offset of start of row + push di ;set aside screen width + mov di,cx ;set aside the column + and cl,0111b ;keep only the column in-byte address + shr di,1 + shr di,1 + shr di,1 ;divide column by 8 to make a byte address + add di,ax ;and point to byte +; +; Calculate font address of character. +; + sub bh,bh + shl bx,1 ;assumes 8 bytes per character; use + shl bx,1 ; a multiply otherwise + shl bx,1 ;offset in font of character + add si,bx ;offset in font segment of character +; +; Set up the GC rotation. +; + mov dx,GC_INDEX + mov al,GC_ROTATE + mov ah,cl + out dx,ax +; +; Set up BH as bit mask for left half, +; BL as rotation for right half. +; + mov bx,0ffffh + shr bh,cl + neg cl + add cl,8 + shl bl,cl +; +; Draw the character, left half first, then right half in the +; succeeding byte, using the data rotation to position the character +; across the byte boundary and then using the bit mask to get the +; proper portion of the character into each byte. +; Does not check for case where character is byte-aligned and +; no rotation and only one write is required. +; + mov bp,FONT_CHARACTER_SIZE + mov dx,GC_INDEX + pop cx ;get back screen width + dec cx + dec cx ; -2 because do two bytes for each char +CharacterLoop: +; +; Set the bit mask for the left half of the character. +; + mov al,GC_BIT_MASK + mov ah,bh + out dx,ax +; +; Get the next character byte & write it to display memory. +; (Left half of character.) +; + mov al,[si] ;get character byte + mov ah,es:[di] ;load latches + stosb ;write character byte +; +; Set the bit mask for the right half of the character. +; + mov al,GC_BIT_MASK + mov ah,bl + out dx,ax +; +; Get the character byte again & write it to display memory. +; (Right half of character.) +; + lodsb ;get character byte + mov ah,es:[di] ;load latches + stosb ;write character byte +; +; Point to next line of character in display memory. +; + add di,cx +; + dec bp + jnz CharacterLoop +; + pop ds + pop bp + pop di + pop si + pop dx + pop cx + pop bx + pop ax + ret +DrawChar endp +; +; Set the pointer to the font to draw from to ES:BP. +; +SelectFont proc near + mov word ptr [FontPointer],bp ;save pointer + mov word ptr [FontPointer+2],es + ret +SelectFont endp +; +cseg ends + end start +``` 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 diff --git a/25-04.md b/25-04.md index 4463027..86efe42 100644 --- a/25-04.md +++ b/25-04.md @@ -12,87 +12,89 @@ pages: 472-474 **LISTING 25.2 L25-2.ASM** - ; Program to illustrate operation of Map Mask register when drawing - ; to memory that already contains data. - ; By Michael Abrash. - ; - stack segment para stack ‘STACK' - db 512 dup(?) - stack ends - ; - EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment - ; - ; EGA register equates. - ; - SC_INDEX equ 3c4h ;SC index register - SC_MAP_MASK equ 2 ;SC map mask register - ; - ; Macro to set indexed register INDEX of SC chip to SETTING. - ; - SETSC macro INDEX, SETTING - mov dx,SC_INDEX - mov al,INDEX - out dx,al - inc dx - mov al,SETTING - out dx,al - dec dx - endm - ; - cseg segment para public ‘CODE#146; - assume cs:cseg - start proc near - ; - ; Select 640x480 graphics mode. - ; - mov ax,012h - int 10h - ; - mov ax,EGA_VIDEO_SEGMENT - mov es,ax ;point to video memory - ; - ; Draw 24 10-scan-line high horizontal bars in green, 10 scan lines apart. - ; - SETSC SC_MAP_MASK,02h ;map mask setting enables only - ; plane 1, the green plane - sub di,di ;start at beginning of video memory - mov al,0ffh - mov bp,24 ;# bars to draw - HorzBarLoop: - mov cx,80*10 ;# bytes per horizontal bar - rep stosb ;draw bar - add di,80*10 ;point to start of next bar - dec bp - jnz HorzBarLoop - ; - ; Fill screen with blue, using Map Mask register to enable writes - ; to blue plane only. - ; - SETSC SC_MAP_MASK,01h ;map mask setting enables only - ; plane 0, the blue plane - sub di,di - mov cx,80*480 ;# bytes per screen - mov al,0ffh - rep stosb ;perform fill (affects only - ; plane 0, the blue plane) - ; - ; Wait for a keystroke. - ; - mov ah,1 - int 21h - ; - ; Restore text mode. - ; - mov ax,03h - int 10h - ; - ; Exit to DOS. - ; - mov ah,4ch - int 21h - start endp - cseg ends - end start +```nasm +; Program to illustrate operation of Map Mask register when drawing +; to memory that already contains data. +; By Michael Abrash. +; +stack segment para stack ‘STACK' + db 512 dup(?) +stack ends +; +EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment +; +; EGA register equates. +; +SC_INDEX equ 3c4h ;SC index register +SC_MAP_MASK equ 2 ;SC map mask register +; +; Macro to set indexed register INDEX of SC chip to SETTING. +; +SETSC macro INDEX, SETTING + mov dx,SC_INDEX + mov al,INDEX + out dx,al + inc dx + mov al,SETTING + out dx,al + dec dx + endm +; +cseg segment para public ‘CODE#146; + assume cs:cseg +start proc near +; +; Select 640x480 graphics mode. +; + mov ax,012h + int 10h +; + mov ax,EGA_VIDEO_SEGMENT + mov es,ax ;point to video memory +; +; Draw 24 10-scan-line high horizontal bars in green, 10 scan lines apart. +; + SETSC SC_MAP_MASK,02h ;map mask setting enables only + ; plane 1, the green plane + sub di,di ;start at beginning of video memory + mov al,0ffh + mov bp,24 ;# bars to draw +HorzBarLoop: + mov cx,80*10 ;# bytes per horizontal bar + rep stosb ;draw bar + add di,80*10 ;point to start of next bar + dec bp + jnz HorzBarLoop +; +; Fill screen with blue, using Map Mask register to enable writes +; to blue plane only. +; + SETSC SC_MAP_MASK,01h ;map mask setting enables only + ; plane 0, the blue plane + sub di,di + mov cx,80*480 ;# bytes per screen + mov al,0ffh + rep stosb ;perform fill (affects only + ; plane 0, the blue plane) +; +; Wait for a keystroke. +; + mov ah,1 + int 21h +; +; Restore text mode. +; + mov ax,03h + int 10h +; +; Exit to DOS. +; + mov ah,4ch + int 21h +start endp +cseg ends + end start +``` #### Setting All Planes to a Single Color {#Heading6} diff --git a/25-05.md b/25-05.md index e37eaae..038f826 100644 --- a/25-05.md +++ b/25-05.md @@ -12,114 +12,116 @@ pages: 474-478 **LISTING 25.3 L25-3.ASM** - ; Program to illustrate operation of set/reset circuitry to force - ; setting of memory that already contains data. - ; By Michael Abrash. - ; - stack segment para stack ‘STACK#146; - db 512 dup(?) - stack ends - ; - EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment - ; - ; EGA register equates. - ; - SC_INDEX equ 3c4h ;SC index register - SC_MAP_MASK equ 2 ;SC map mask register - GC_INDEX equ 3ceh ;GC index register - GC_SET_RESET equ 0 ;GC set/reset register - GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register - ; - ; Macro to set indexed register INDEX of SC chip to SETTING. - ; - SETSC macro INDEX, SETTING - mov dx,SC_INDEX - mov al,INDEX - out dx,al - inc dx - mov al,SETTING - out dx,al - dec dx - endm - ; - ; Macro to set indexed register INDEX of GC chip to SETTING. - ; - SETGC macro INDEX, SETTING - mov dx,GC_INDEX - mov al,INDEX - out dx,al - inc dx - mov al,SETTING - out dx,al - dec dx - endm - ; - cseg segment para public ‘CODE#146; - assume cs:cseg - start proc near - ; - ; Select 640x480 graphics mode. - ; - mov ax,012h - int 10h - ; - mov ax,EGA_VIDEO_SEGMENT - mov es,ax ;point to video memory - ; - ; Draw 24 10-scan-line high horizontal bars in green, 10 scan lines apart. - ; - SETSC SC_MAP_MASK,02h ;map mask setting enables only - ; plane 1, the green plane - sub di,di ;start at beginning of video memory - mov al,0ffh - mov bp,24 ;# bars to draw - HorzBarLoop: - mov cx,80*10 ;# bytes per horizontal bar - rep stosb ;draw bar - add di,80*10 ;point to start of next bar - dec bp - jnz HorzBarLoop - ; - ; Fill screen with blue, using set/reset to force plane 0 to 1#146;s and all - ; other plane to 0#146;s. - ; - SETSC SC_MAP_MASK,0fh ;must set map mask to enable all - ; planes, so set/reset values can - ; be written to memory - SETGC GC_ENABLE_SET_RESET,0fh ;CPU data to all planes will be - ; replaced by set/reset value - SETGC GC_SET_RESET,01h ;set/reset value is 0ffh for plane 0 - ; (the blue plane) and 0 for other - ; planes - sub di,di - mov cx,80*480 ;# bytes per screen - mov al,0ffh ;since set/reset is enabled for all - ; planes, the CPU data is ignored- - ; only the act of writing is - ; important - rep stosb ;perform fill (affects all planes) - ; - ; Turn off set/reset. - ; - SETGC GC_ENABLE_SET_RESET,0 - ; - ; Wait for a keystroke. - ; - mov ah,1 - int 21h - ; - ; Restore text mode. - ; - mov ax,03h - int 10h - ; - ; Exit to DOS. - ; - mov ah,4ch - int 21h - start endp - cseg ends - end start +```nasm +; Program to illustrate operation of set/reset circuitry to force +; setting of memory that already contains data. +; By Michael Abrash. +; +stack segment para stack ‘STACK#146; + db 512 dup(?) +stack ends +; +EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment +; +; EGA register equates. +; +SC_INDEX equ 3c4h ;SC index register +SC_MAP_MASK equ 2 ;SC map mask register +GC_INDEX equ 3ceh ;GC index register +GC_SET_RESET equ 0 ;GC set/reset register +GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register +; +; Macro to set indexed register INDEX of SC chip to SETTING. +; +SETSC macro INDEX, SETTING + mov dx,SC_INDEX + mov al,INDEX + out dx,al + inc dx + mov al,SETTING + out dx,al + dec dx + endm +; +; Macro to set indexed register INDEX of GC chip to SETTING. +; +SETGC macro INDEX, SETTING + mov dx,GC_INDEX + mov al,INDEX + out dx,al + inc dx + mov al,SETTING + out dx,al + dec dx + endm +; +cseg segment para public ‘CODE#146; + assume cs:cseg +start proc near +; +; Select 640x480 graphics mode. +; + mov ax,012h + int 10h +; + mov ax,EGA_VIDEO_SEGMENT + mov es,ax ;point to video memory +; +; Draw 24 10-scan-line high horizontal bars in green, 10 scan lines apart. +; + SETSC SC_MAP_MASK,02h ;map mask setting enables only + ; plane 1, the green plane + sub di,di ;start at beginning of video memory + mov al,0ffh + mov bp,24 ;# bars to draw +HorzBarLoop: + mov cx,80*10 ;# bytes per horizontal bar + rep stosb ;draw bar + add di,80*10 ;point to start of next bar + dec bp + jnz HorzBarLoop +; +; Fill screen with blue, using set/reset to force plane 0 to 1#146;s and all +; other plane to 0#146;s. +; + SETSC SC_MAP_MASK,0fh ;must set map mask to enable all + ; planes, so set/reset values can + ; be written to memory + SETGC GC_ENABLE_SET_RESET,0fh ;CPU data to all planes will be + ; replaced by set/reset value + SETGC GC_SET_RESET,01h ;set/reset value is 0ffh for plane 0 + ; (the blue plane) and 0 for other + ; planes + sub di,di + mov cx,80*480 ;# bytes per screen + mov al,0ffh ;since set/reset is enabled for all + ; planes, the CPU data is ignored- + ; only the act of writing is + ; important + rep stosb ;perform fill (affects all planes) +; +; Turn off set/reset. +; + SETGC GC_ENABLE_SET_RESET,0 +; +; Wait for a keystroke. +; + mov ah,1 + int 21h +; +; Restore text mode. +; + mov ax,03h + int 10h +; +; Exit to DOS. +; + mov ah,4ch + int 21h +start endp +cseg ends + end start +``` #### Manipulating Planes Individually {#Heading7} @@ -142,111 +144,113 @@ be used to control individual pixels. **LISTING 25.4 L25-4.ASM** - ; Program to illustrate operation of set/reset circuitry in conjunction - ; with CPU data to modify setting of memory that already contains data. - ; By Michael Abrash. - ; - stack segment para stack ‘STACK#146; - db 512 dup(?) - stack ends - ; - EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment - ; - ; EGA register equates. - ; - SC_INDEX equ 3c4h ;SC index register - SC_MAP_MASK equ 2 ;SC map mask register - GC_INDEX equ 3ceh ;GC index register - GC_SET_RESET equ 0 ;GC set/reset register - GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register - ; - ; Macro to set indexed register INDEX of SC chip to SETTING. - ; - SETSC macro INDEX, SETTING - mov dx,SC_INDEX - mov al,INDEX - out dx,al - inc dx - mov al,SETTING - out dx,al - dec dx - endm - ; - ; Macro to set indexed register INDEX of GC chip to SETTING. - ; - SETGC macro INDEX, SETTING - mov dx,GC_INDEX - mov al,INDEX - out dx,al - inc dx - mov al,SETTING - out dx,al - dec dx - endm - ; - cseg segment para public ‘CODE#146; - assume cs:cseg - start proc near - ; - ; Select 640x350 graphics mode. - ; - mov ax,010h - int 10h - ; - mov ax,EGA_VIDEO_SEGMENT - mov es,ax ;point to video memory - ; - ; Draw 18 10-scan-line high horizontal bars in green, 10 scan lines apart. - ; - SETSC SC_MAP_MASK,02h;map mask setting enables only - ; plane 1, the green plane - sub di,di;start at beginning of video memory - mov al,0ffh - mov bp,18;# bars to draw - HorzBarLoop: - mov cx,80*10;# bytes per horizontal bar - rep stosb;draw bar - add di,80*10;point to start of next bar - dec bp - jnz HorzBarLoop - ; - ; Fill screen with alternating bars of red and brown, using CPU data - ; to set plane 1 and set/reset to set planes 0, 2 & 3. - ; - SETSC SC_MAP_MASK,0fh ;must set map mask to enable all - ; planes, so set/reset values can - ; be written to planes 0, 2 & 3 - ; and CPU data can be written to - ; plane 1 (the green plane) - SETGC GC_ENABLE_SET_RESET,0dh ;CPU data to planes 0, 2 & 3 will be - ; replaced by set/reset value - SETGC GC_SET_RESET,04h ;set/reset value is 0ffh for plane 2 - ; (the red plane) and 0 for other - ; planes - sub di,di - mov cx,80*350/2 ;# words per screen - mov ax,07e0h ;CPU data controls only plane 1; - ; set/reset controls other planes - rep stosw ;perform fill (affects all planes) - ; - ; Turn off set/reset. - ; - SETGC GC_ENABLE_SET_RESET,0 - ; - ; Wait for a keystroke. - ; - mov ah,1 - int 21h - ; - ; Restore text mode. - ; - mov ax,03h - int 10h - ; - ; Exit to DOS. - ; - mov ah,4ch - int 21h - start endp - cseg ends - end start +```nasm +; Program to illustrate operation of set/reset circuitry in conjunction +; with CPU data to modify setting of memory that already contains data. +; By Michael Abrash. +; +stack segment para stack ‘STACK#146; + db 512 dup(?) +stack ends +; +EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment +; +; EGA register equates. +; +SC_INDEX equ 3c4h ;SC index register +SC_MAP_MASK equ 2 ;SC map mask register +GC_INDEX equ 3ceh ;GC index register +GC_SET_RESET equ 0 ;GC set/reset register +GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register +; +; Macro to set indexed register INDEX of SC chip to SETTING. +; +SETSC macro INDEX, SETTING + mov dx,SC_INDEX + mov al,INDEX + out dx,al + inc dx + mov al,SETTING + out dx,al + dec dx + endm +; +; Macro to set indexed register INDEX of GC chip to SETTING. +; +SETGC macro INDEX, SETTING + mov dx,GC_INDEX + mov al,INDEX + out dx,al + inc dx + mov al,SETTING + out dx,al + dec dx + endm +; +cseg segment para public ‘CODE#146; + assume cs:cseg +start proc near +; +; Select 640x350 graphics mode. +; + mov ax,010h + int 10h +; + mov ax,EGA_VIDEO_SEGMENT + mov es,ax ;point to video memory +; +; Draw 18 10-scan-line high horizontal bars in green, 10 scan lines apart. +; + SETSC SC_MAP_MASK,02h;map mask setting enables only +; plane 1, the green plane + sub di,di;start at beginning of video memory + mov al,0ffh + mov bp,18;# bars to draw +HorzBarLoop: + mov cx,80*10;# bytes per horizontal bar + rep stosb;draw bar + add di,80*10;point to start of next bar + dec bp + jnz HorzBarLoop +; +; Fill screen with alternating bars of red and brown, using CPU data +; to set plane 1 and set/reset to set planes 0, 2 & 3. +; + SETSC SC_MAP_MASK,0fh ;must set map mask to enable all + ; planes, so set/reset values can + ; be written to planes 0, 2 & 3 + ; and CPU data can be written to + ; plane 1 (the green plane) + SETGC GC_ENABLE_SET_RESET,0dh ;CPU data to planes 0, 2 & 3 will be + ; replaced by set/reset value + SETGC GC_SET_RESET,04h ;set/reset value is 0ffh for plane 2 + ; (the red plane) and 0 for other + ; planes + sub di,di + mov cx,80*350/2 ;# words per screen + mov ax,07e0h ;CPU data controls only plane 1; + ; set/reset controls other planes + rep stosw ;perform fill (affects all planes) +; +; Turn off set/reset. +; + SETGC GC_ENABLE_SET_RESET,0 +; +; Wait for a keystroke. +; + mov ah,1 + int 21h +; +; Restore text mode. +; + mov ax,03h + int 10h +; +; Exit to DOS. +; + mov ah,4ch + int 21h +start endp +cseg ends + end start +``` diff --git a/26-02.md b/26-02.md index f30853b..cc03eca 100644 --- a/26-02.md +++ b/26-02.md @@ -12,296 +12,298 @@ pages: 484-489 **LISTING 26.1 L26-1.ASM** - ; 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 - ; 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' - db 512 dup(?) - stack ends - ; - VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment - SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable - FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char - ; - ; VGA register equates. - ; - SC_INDEX equ 3c4h ;SC index register - SC_MAP_MASK equ 2 ;SC map mask register index - GC_INDEX equ 3ceh ;GC index register - GC_SET_RESET equ 0 ;GC set/reset register index - GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index - GC_ROTATE equ 3 ;GC data rotate/logical function - ; register index - GC_MODE equ 5 ;GC Mode register - GC_BIT_MASK equ 8 ;GC bit mask register index - ; - 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. - FontPointer dd ? ;font offset - dseg ends - ; - cseg segment para public ‘CODE' - assume cs:cseg, ds:dseg - start proc near - mov ax,dseg - mov ds,ax - ; - ; Select 640x480 graphics mode. - ; - mov ax,012h - int 10h - ; - ; Set the screen to all blue, using the readability of VGA registers - ; to preserve reserved bits. - ; - mov dx,GC_INDEX - mov al,GC_SET_RESET - out dx,al - inc dx - in al,dx - and al,0f0h - or al,1 ;blue plane only set, others reset - out dx,al - dec dx - mov al,GC_ENABLE_SET_RESET - out dx,al - inc dx - in al,dx - and al,0f0h - or al,0fh ;enable set/reset for all planes - out dx,al - mov dx,VGA_VIDEO_SEGMENT - mov es,dx ;point to display memory - mov di,0 - mov cx,8000h ;fill all 32k words - mov ax,0ffffh ;because of set/reset, the value - ; written actually doesn't matter - rep stosw ;fill with blue - ; - ; Set driver to use the 8x8 font. - ; - mov ah,11h ;VGA BIOS character generator function, - mov al,30h ; return info subfunction - mov bh,3 ;get 8x8 font pointer - int 10h - call SelectFont - ; - ; Print the test string, cycling through colors. - ; - mov si,offset TestString - mov bx,TEST_TEXT_ROW - mov cx,TEST_TEXT_COL - mov ah,0 ;start with color 0 - StringOutLoop: - lodsb - and al,al - jz StringOutDone - push ax ;preserve color - call DrawChar - pop ax ;restore color - inc ah ;next color - and ah,0fh ;colors range from 0 to 15 - add cx,TEST_TEXT_WIDTH - jmp StringOutLoop - StringOutDone: - ; - ; Wait for a key, then set to text mode & end. - ; - mov ah,1 - int 21h ;wait for a key - mov ax,3 - int 10h ;restore text mode - ; - ; Exit to DOS. - ; - mov ah,4ch - int 21h - Start endp - ; - ; Subroutine to draw a text character in a linear graphics mode - ; (0Dh, 0Eh, 0Fh, 010h, 012h). Background around the pixels that - ; make up the character is preserved. - ; Font used should be pointed to by FontPointer. - ; - ; Input: - ; AL = character to draw - ; AH = color to draw character in (0-15) - ; BX = row to draw text character at - ; CX = column to draw text character at - ; - ; Forces ALU function to "move". - ; Forces write mode 3. - ; - DrawChar proc near - push ax - push bx - push cx - push dx - push si - push di - push bp - push ds - push ax ;preserve character to draw in AL - ; - ; Set up set/reset to produce character color, using the readability - ; of VGA register to preserve the setting of reserved bits 7-4. - ; - mov dx,GC_INDEX - mov al,GC_SET_RESET - out dx,al - inc dx - in al,dx - and al,0f0h - and ah,0fh - or al,ah - out dx,al - ; - ; Select write mode 3, using the readability of VGA registers - ; to leave bits other than the write mode bits unchanged. - ; - mov dx,GC_INDEX - mov al,GC_MODE - out dx,al - inc dx - in al,dx - or al,3 - out dx,al - ; - ; Set DS:SI to point to font and ES to point to display memory. - ; - lds si,[FontPointer] ;point to font - mov dx,VGA_VIDEO_SEGMENT - mov es,dx ;point to display memory - ; - ; Calculate screen address of byte character starts in. - ; - pop ax ;get back character to draw in AL +```nasm +; 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 +; 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' + db 512 dup(?) +stack ends +; +VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment +SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable +FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char +; +; VGA register equates. +; +SC_INDEX equ 3c4h ;SC index register +SC_MAP_MASK equ 2 ;SC map mask register index +GC_INDEX equ 3ceh ;GC index register +GC_SET_RESET equ 0 ;GC set/reset register index +GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index +GC_ROTATE equ 3 ;GC data rotate/logical function + ; register index +GC_MODE equ 5 ;GC Mode register +GC_BIT_MASK equ 8 ;GC bit mask register index +; +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. +FontPointer dd ? ;font offset +dseg ends +; +cseg segment para public ‘CODE' + assume cs:cseg, ds:dseg +start proc near + mov ax,dseg + mov ds,ax +; +; Select 640x480 graphics mode. +; + mov ax,012h + int 10h +; +; Set the screen to all blue, using the readability of VGA registers +; to preserve reserved bits. +; + mov dx,GC_INDEX + mov al,GC_SET_RESET + out dx,al + inc dx + in al,dx + and al,0f0h + or al,1 ;blue plane only set, others reset + out dx,al + dec dx + mov al,GC_ENABLE_SET_RESET + out dx,al + inc dx + in al,dx + and al,0f0h + or al,0fh ;enable set/reset for all planes + out dx,al + mov dx,VGA_VIDEO_SEGMENT + mov es,dx ;point to display memory + mov di,0 + mov cx,8000h ;fill all 32k words + mov ax,0ffffh ;because of set/reset, the value + ; written actually doesn't matter + rep stosw ;fill with blue +; +; Set driver to use the 8x8 font. +; + mov ah,11h ;VGA BIOS character generator function, + mov al,30h ; return info subfunction + mov bh,3 ;get 8x8 font pointer + int 10h + call SelectFont +; +; Print the test string, cycling through colors. +; + mov si,offset TestString + mov bx,TEST_TEXT_ROW + mov cx,TEST_TEXT_COL + mov ah,0 ;start with color 0 +StringOutLoop: + lodsb + and al,al + jz StringOutDone + push ax ;preserve color + call DrawChar + pop ax ;restore color + inc ah ;next color + and ah,0fh ;colors range from 0 to 15 + add cx,TEST_TEXT_WIDTH + jmp StringOutLoop +StringOutDone: +; +; Wait for a key, then set to text mode & end. +; + mov ah,1 + int 21h ;wait for a key + mov ax,3 + int 10h ;restore text mode +; +; Exit to DOS. +; + mov ah,4ch + int 21h +Start endp +; +; Subroutine to draw a text character in a linear graphics mode +; (0Dh, 0Eh, 0Fh, 010h, 012h). Background around the pixels that +; make up the character is preserved. +; Font used should be pointed to by FontPointer. +; +; Input: +; AL = character to draw +; AH = color to draw character in (0-15) +; BX = row to draw text character at +; CX = column to draw text character at +; +; Forces ALU function to "move". +; Forces write mode 3. +; +DrawChar proc near + push ax + push bx + push cx + push dx + push si + push di + push bp + push ds + push ax ;preserve character to draw in AL +; +; Set up set/reset to produce character color, using the readability +; of VGA register to preserve the setting of reserved bits 7-4. +; + mov dx,GC_INDEX + mov al,GC_SET_RESET + out dx,al + inc dx + in al,dx + and al,0f0h + and ah,0fh + or al,ah + out dx,al +; +; Select write mode 3, using the readability of VGA registers +; to leave bits other than the write mode bits unchanged. +; + mov dx,GC_INDEX + mov al,GC_MODE + out dx,al + inc dx + in al,dx + or al,3 + out dx,al +; +; Set DS:SI to point to font and ES to point to display memory. +; + lds si,[FontPointer] ;point to font + mov dx,VGA_VIDEO_SEGMENT + mov es,dx ;point to display memory +; +; Calculate screen address of byte character starts in. +; + pop ax ;get back character to draw in AL - push ds ;point to BIOS data segment - sub dx,dx - mov ds,dx - xchg ax,bx - mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS - ; screen width - pop ds - mul di ;calculate offset of start of row - push di ;set aside screen width - mov di,cx ;set aside the column - and cl,0111b ;keep only the column in-byte address - shr di,1 - shr di,1 - shr di,1 ;divide column by 8 to make a byte address - add di,ax ;and point to byte - ; - ; Calculate font address of character. - ; - sub bh,bh - shl bx,1 ;assumes 8 bytes per character; use - shl bx,1 ; a multiply otherwise - shl bx,1 ;offset in font of character - add si,bx ;offset in font segment of character - ; - ; 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 - ; readability of VGA registers to leave reserved bits unchanged. - ; - mov dx,GC_INDEX - mov al,GC_ROTATE - out dx,al - inc dx - in al,dx - and al,0e0h - or al,cl - out dx,al - ; - ; Set up BH as bit mask for left half, BL as rotation for right half. - ; - mov bx,0ffffh - shr bh,cl - neg cl - add cl,8 - shl bl,cl - ; - ; Draw the character, left half first, then right half in the - ; succeeding byte, using the data rotation to position the character - ; across the byte boundary and then using write mode 3 to combine the - ; character data with the bit mask to allow the set/reset value (the - ; character color) through only for the proper portion (where the - ; font bits for the character are 1) of the character for each byte. - ; Wherever the font bits for the character are 0, the background - ; color is preserved. - ; Does not check for case where character is byte-aligned and - ; no rotation and only one write is required. - ; - mov bp,FONT_CHARACTER_SIZE - mov dx,GC_INDEX - pop cx ;get back screen width - dec cx - dec cx ; -2 because do two bytes for each char - CharacterLoop: - ; - ; Set the bit mask for the left half of the character. - ; - mov al,GC_BIT_MASK - mov ah,bh - out dx,ax - ; - ; Get the next character byte & write it to display memory. - ; (Left half of character.) - ; - mov al,[si] ;get character byte - mov ah,es:[di] ;load latches - stosb ;write character byte - ; - ; Set the bit mask for the right half of the character. - ; - mov al,GC_BIT_MASK - mov ah,bl - out dx,ax - ; - ; Get the character byte again & write it to display memory. - ; (Right half of character.) - ; - lodsb ;get character byte - mov ah,es:[di] ;load latches - stosb ;write character byte - ; - ; Point to next line of character in display memory. - ; - add di,cx - ; - dec bp - jnz CharacterLoop - ; - pop ds - pop bp - pop di - pop si - pop dx - pop cx - pop bx - pop ax - ret - DrawChar endp - ; - ; Set the pointer to the font to draw from to ES:BP. - ; - SelectFont proc near - mov word ptr [FontPointer],bp ;save pointer - mov word ptr [FontPointer+2],es - ret - SelectFont endp - ; - cseg ends - end start + push ds ;point to BIOS data segment + sub dx,dx + mov ds,dx + xchg ax,bx + mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS + ; screen width + pop ds + mul di ;calculate offset of start of row + push di ;set aside screen width + mov di,cx ;set aside the column + and cl,0111b ;keep only the column in-byte address + shr di,1 + shr di,1 + shr di,1 ;divide column by 8 to make a byte address + add di,ax ;and point to byte +; +; Calculate font address of character. +; + sub bh,bh + shl bx,1 ;assumes 8 bytes per character; use + shl bx,1 ; a multiply otherwise + shl bx,1 ;offset in font of character + add si,bx ;offset in font segment of character +; +; 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 +; readability of VGA registers to leave reserved bits unchanged. +; + mov dx,GC_INDEX + mov al,GC_ROTATE + out dx,al + inc dx + in al,dx + and al,0e0h + or al,cl + out dx,al +; +; Set up BH as bit mask for left half, BL as rotation for right half. +; + mov bx,0ffffh + shr bh,cl + neg cl + add cl,8 + shl bl,cl +; +; Draw the character, left half first, then right half in the +; succeeding byte, using the data rotation to position the character +; across the byte boundary and then using write mode 3 to combine the +; character data with the bit mask to allow the set/reset value (the +; character color) through only for the proper portion (where the +; font bits for the character are 1) of the character for each byte. +; Wherever the font bits for the character are 0, the background +; color is preserved. +; Does not check for case where character is byte-aligned and +; no rotation and only one write is required. +; + mov bp,FONT_CHARACTER_SIZE + mov dx,GC_INDEX + pop cx ;get back screen width + dec cx + dec cx ; -2 because do two bytes for each char +CharacterLoop: +; +; Set the bit mask for the left half of the character. +; + mov al,GC_BIT_MASK + mov ah,bh + out dx,ax +; +; Get the next character byte & write it to display memory. +; (Left half of character.) +; + mov al,[si] ;get character byte + mov ah,es:[di] ;load latches + stosb ;write character byte +; +; Set the bit mask for the right half of the character. +; + mov al,GC_BIT_MASK + mov ah,bl + out dx,ax +; +; Get the character byte again & write it to display memory. +; (Right half of character.) +; + lodsb ;get character byte + mov ah,es:[di] ;load latches + stosb ;write character byte +; +; Point to next line of character in display memory. +; + add di,cx +; + dec bp + jnz CharacterLoop +; + pop ds + pop bp + pop di + pop si + pop dx + pop cx + pop bx + pop ax + ret +DrawChar endp +; +; Set the pointer to the font to draw from to ES:BP. +; +SelectFont proc near + mov word ptr [FontPointer],bp ;save pointer + mov word ptr [FontPointer+2],es + ret +SelectFont endp +; +cseg ends + end start +``` diff --git a/26-03.md b/26-03.md index b8c1585..509d257 100644 --- a/26-03.md +++ b/26-03.md @@ -80,324 +80,326 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into **LISTING 26.2 L26-2.ASM** - ; 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. - ; 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' - db 512 dup(?) - stack ends - ; - VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment - SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable - FONT_CHARACTER_SIZE equ 14 ;# bytes in each font char - ; - ; VGA register equates. - ; - SC_INDEX equ 3c4h ;SC index register - SC_MAP_MASK equ 2 ;SC map mask register index - GC_INDEX equ 3ceh ;GC index register - GC_SET_RESET equ 0 ;GC set/reset register index - GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index - GC_ROTATE equ 3 ;GC data rotate/logical function - ; register index - GC_MODE equ 5 ;GC Mode register - GC_BIT_MASK equ 8 ;GC bit mask register index - ; - 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. - FontPointer dd ? ;font offset - dseg ends - ; - cseg segment para public ‘CODE' - assume cs:cseg, ds:dseg - start proc near - mov ax,dseg - mov ds,ax - ; - ; Select 640x480 graphics mode. - ; - mov ax,012h - int 10h - ; - ; Set the screen to all blue, using the readability of VGA registers - ; to preserve reserved bits. - ; - mov dx,GC_INDEX - mov al,GC_SET_RESET - out dx,al - inc dx - in al,dx - and al,0f0h - or al,1 ;blue plane only set, others reset - out dx,al - dec dx - mov al,GC_ENABLE_SET_RESET - out dx,al - inc dx - in al,dx - and al,0f0h - or al,0fh ;enable set/reset for all planes - out dx,al - mov dx,VGA_VIDEO_SEGMENT - mov es,dx ;point to display memory - mov di,0 - mov cx,8000h ;fill all 32k words - mov ax,0ffffh ;because of set/reset, the value - ; written actually doesn't matter - rep stosw ;fill with blue - ; - ; Set driver to use the 8x14 font. - ; - mov ah,11h ;VGA BIOS character generator function, - mov al,30h ; return info subfunction - mov bh,2 ;get 8x14 font pointer - int 10h - call SelectFont - ; - ; Print the test string. - ; - mov si,offset TestString - mov bx,TEST_TEXT_ROW - mov cx,TEST_TEXT_COL - mov ah,TEST_TEXT_COLOR - call DrawString - ; - ; Wait for a key, then set to text mode & end. - ; - mov ah,1 - int 21h ;wait for a key - mov ax,3 - int 10h ;restore text mode - ; - ; Exit to DOS. - ; - mov ah,4ch - int 21h - Start endp - ; - ; Subroutine to draw a text string left-to-right in a linear - ; graphics mode (0Dh, 0Eh, 0Fh, 010h, 012h) with 8-dot-wide - ; characters. Background around the pixels that make up the - ; characters is preserved. - ; Font used should be pointed to by FontPointer. - ; - ; Input: - ; AH = color to draw string in - ; BX = row to draw string on - ; CX = column to start string at - ; DS:SI = string to draw - ; - ; Forces ALU function to "move". - ; Forces write mode 3. - ; - DrawString proc near - push ax - push bx - push cx - push dx - push si - push di - push bp - push ds - ; - ; Set up set/reset to produce character color, using the readability - ; of VGA register to preserve the setting of reserved bits 7-4. - ; - mov dx,GC_INDEX - mov al,GC_SET_RESET - out dx,al - inc dx - in al,dx - and al,0f0h - and ah,0fh - or al,ah - out dx,al - ; - ; Select write mode 3, using the readability of VGA registers - ; to leave bits other than the write mode bits unchanged. - ; - mov dx,GC_INDEX - mov al,GC_MODE - out dx,al - inc dx - in al,dx - or al,3 - out dx,al - mov dx,VGA_VIDEO_SEGMENT - mov es,dx ;point to display memory - ; - ; Calculate screen address of byte character starts in. - ; - push ds ;point to BIOS data segment - sub dx,dx - mov ds,dx - mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS - ; screen width - pop ds - mov ax,bx ;row - mul di ;calculate offset of start of row - push di ;set aside screen width - mov di,cx ;set aside the column - and cl,0111b ;keep only the column in-byte address - shr di,1 - shr di,1 - shr di,1 ;divide column by 8 to make a byte address - add di,ax ;and point to byte - ; - ; 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 - ; readability of VGA registers to leave reserved bits unchanged. - ; - mov dx,GC_INDEX - mov al,GC_ROTATE - out dx,al - inc dx - in al,dx - and al,0e0h - or al,cl - out dx,al - ; - ; Set up BH as bit mask for left half, BL as rotation for right half. - ; - mov bx,0ffffh - shr bh,cl - neg cl - add cl,8 - shl bl,cl - ; - ; Draw all characters, left portion first, then right portion in the - ; succeeding byte, using the data rotation to position the character - ; across the byte boundary and then using write mode 3 to combine the - ; character data with the bit mask to allow the set/reset value (the - ; character color) through only for the proper portion (where the - ; font bits for the character are 1) of the character for each byte. - ; Wherever the font bits for the character are 0, the background - ; color is preserved. - ; Does not check for case where character is byte-aligned and - ; no rotation and only one write is required. - ; - ; Draw the left portion of each character in the string. - ; - pop cx ;get back screen width - push si - push di - push bx - ; - ; Set the bit mask for the left half of the character. - ; - mov dx,GC_INDEX - mov al,GC_BIT_MASK - mov ah,bh - out dx,ax - LeftHalfLoop: - lodsb - and al,al - jz LeftHalfLoopDone - call CharacterUp - inc di ;point to next character location - jmp LeftHalfLoop - LeftHalfLoopDone: - pop bx - pop di - pop si - ; - ; Draw the right portion of each character in the string. - ; - inc di ;right portion of each character is across - ; byte boundary - ; - ; Set the bit mask for the right half of the character. - ; - mov dx,GC_INDEX - mov al,GC_BIT_MASK - mov ah,bl - out dx,ax - RightHalfLoop: - lodsb - and al,al - jz RightHalfLoopDone - call CharacterUp - inc di ;point to next character location - jmp RightHalfLoop - RightHalfLoopDone: - ; - pop ds - pop bp - pop di - pop si - pop dx - pop cx - pop bx - pop ax - ret - DrawString endp - ; - ; Draw a character. - ; - ; Input: - ; AL = character - ; CX = screen width - ; ES:DI = address to draw character at - ; - CharacterUp proc near - push cx - push si - push di - push ds - ; - ; Set DS:SI to point to font and ES to point to display memory. - ; - lds si,[FontPointer] ;point to font - ; - ; Calculate font address of character. - ; - mov bl,14 ;14 bytes per character - mul bl - add si,ax ;offset in font segment of character +```nasm +; 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. +; 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' + db 512 dup(?) +stack ends +; +VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment +SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable +FONT_CHARACTER_SIZE equ 14 ;# bytes in each font char +; +; VGA register equates. +; +SC_INDEX equ 3c4h ;SC index register +SC_MAP_MASK equ 2 ;SC map mask register index +GC_INDEX equ 3ceh ;GC index register +GC_SET_RESET equ 0 ;GC set/reset register index +GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index +GC_ROTATE equ 3 ;GC data rotate/logical function + ; register index +GC_MODE equ 5 ;GC Mode register +GC_BIT_MASK equ 8 ;GC bit mask register index +; +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. +FontPointer dd ? ;font offset +dseg ends +; +cseg segment para public ‘CODE' + assume cs:cseg, ds:dseg +start proc near + mov ax,dseg + mov ds,ax +; +; Select 640x480 graphics mode. +; + mov ax,012h + int 10h +; +; Set the screen to all blue, using the readability of VGA registers +; to preserve reserved bits. +; + mov dx,GC_INDEX + mov al,GC_SET_RESET + out dx,al + inc dx + in al,dx + and al,0f0h + or al,1 ;blue plane only set, others reset + out dx,al + dec dx + mov al,GC_ENABLE_SET_RESET + out dx,al + inc dx + in al,dx + and al,0f0h + or al,0fh ;enable set/reset for all planes + out dx,al + mov dx,VGA_VIDEO_SEGMENT + mov es,dx ;point to display memory + mov di,0 + mov cx,8000h ;fill all 32k words + mov ax,0ffffh ;because of set/reset, the value + ; written actually doesn't matter + rep stosw ;fill with blue +; +; Set driver to use the 8x14 font. +; + mov ah,11h ;VGA BIOS character generator function, + mov al,30h ; return info subfunction + mov bh,2 ;get 8x14 font pointer + int 10h + call SelectFont +; +; Print the test string. +; + mov si,offset TestString + mov bx,TEST_TEXT_ROW + mov cx,TEST_TEXT_COL + mov ah,TEST_TEXT_COLOR + call DrawString +; +; Wait for a key, then set to text mode & end. +; + mov ah,1 + int 21h ;wait for a key + mov ax,3 + int 10h ;restore text mode +; +; Exit to DOS. +; + mov ah,4ch + int 21h +Start endp +; +; Subroutine to draw a text string left-to-right in a linear +; graphics mode (0Dh, 0Eh, 0Fh, 010h, 012h) with 8-dot-wide +; characters. Background around the pixels that make up the +; characters is preserved. +; Font used should be pointed to by FontPointer. +; +; Input: +; AH = color to draw string in +; BX = row to draw string on +; CX = column to start string at +; DS:SI = string to draw +; +; Forces ALU function to "move". +; Forces write mode 3. +; +DrawString proc near + push ax + push bx + push cx + push dx + push si + push di + push bp + push ds +; +; Set up set/reset to produce character color, using the readability +; of VGA register to preserve the setting of reserved bits 7-4. +; + mov dx,GC_INDEX + mov al,GC_SET_RESET + out dx,al + inc dx + in al,dx + and al,0f0h + and ah,0fh + or al,ah + out dx,al +; +; Select write mode 3, using the readability of VGA registers +; to leave bits other than the write mode bits unchanged. +; + mov dx,GC_INDEX + mov al,GC_MODE + out dx,al + inc dx + in al,dx + or al,3 + out dx,al + mov dx,VGA_VIDEO_SEGMENT + mov es,dx ;point to display memory +; +; Calculate screen address of byte character starts in. +; + push ds ;point to BIOS data segment + sub dx,dx + mov ds,dx + mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS + ; screen width + pop ds + mov ax,bx ;row + mul di ;calculate offset of start of row + push di ;set aside screen width + mov di,cx ;set aside the column + and cl,0111b ;keep only the column in-byte address + shr di,1 + shr di,1 + shr di,1 ;divide column by 8 to make a byte address + add di,ax ;and point to byte +; +; 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 +; readability of VGA registers to leave reserved bits unchanged. +; + mov dx,GC_INDEX + mov al,GC_ROTATE + out dx,al + inc dx + in al,dx + and al,0e0h + or al,cl + out dx,al +; +; Set up BH as bit mask for left half, BL as rotation for right half. +; + mov bx,0ffffh + shr bh,cl + neg cl + add cl,8 + shl bl,cl +; +; Draw all characters, left portion first, then right portion in the +; succeeding byte, using the data rotation to position the character +; across the byte boundary and then using write mode 3 to combine the +; character data with the bit mask to allow the set/reset value (the +; character color) through only for the proper portion (where the +; font bits for the character are 1) of the character for each byte. +; Wherever the font bits for the character are 0, the background +; color is preserved. +; Does not check for case where character is byte-aligned and +; no rotation and only one write is required. +; +; Draw the left portion of each character in the string. +; + pop cx ;get back screen width + push si + push di + push bx +; +; Set the bit mask for the left half of the character. +; + mov dx,GC_INDEX + mov al,GC_BIT_MASK + mov ah,bh + out dx,ax +LeftHalfLoop: + lodsb + and al,al + jz LeftHalfLoopDone + call CharacterUp + inc di ;point to next character location + jmp LeftHalfLoop +LeftHalfLoopDone: + pop bx + pop di + pop si +; +; Draw the right portion of each character in the string. +; + inc di ;right portion of each character is across + ; byte boundary +; +; Set the bit mask for the right half of the character. +; + mov dx,GC_INDEX + mov al,GC_BIT_MASK + mov ah,bl + out dx,ax +RightHalfLoop: + lodsb + and al,al + jz RightHalfLoopDone + call CharacterUp + inc di ;point to next character location + jmp RightHalfLoop +RightHalfLoopDone: +; + pop ds + pop bp + pop di + pop si + pop dx + pop cx + pop bx + pop ax + ret +DrawString endp +; +; Draw a character. +; +; Input: +; AL = character +; CX = screen width +; ES:DI = address to draw character at +; +CharacterUp proc near + push cx + push si + push di + push ds +; +; Set DS:SI to point to font and ES to point to display memory. +; + lds si,[FontPointer] ;point to font +; +; Calculate font address of character. +; + mov bl,14 ;14 bytes per character + mul bl + add si,ax ;offset in font segment of character - mov bp,FONT_CHARACTER_SIZE - dec cx ; -1 because one byte per char - CharacterLoop: - lodsb ;get character byte - mov ah,es:[di] ;load latches - stosb ;write character byte - ; - ; Point to next line of character in display memory. - ; - add di,cx - ; - dec bp - jnz CharacterLoop - ; - pop ds - pop di - pop si - pop cx - ret - CharacterUp endp - ; - ; Set the pointer to the font to draw from to ES:BP. - ; - SelectFont proc near - mov word ptr [FontPointer],bp ;save pointer - mov word ptr [FontPointer+2],es - ret - SelectFont endp - ; - cseg ends - end start + mov bp,FONT_CHARACTER_SIZE + dec cx ; -1 because one byte per char +CharacterLoop: + lodsb ;get character byte + mov ah,es:[di] ;load latches + stosb ;write character byte +; +; Point to next line of character in display memory. +; + add di,cx +; + dec bp + jnz CharacterLoop +; + pop ds + pop di + pop si + pop cx + ret +CharacterUp endp +; +; Set the pointer to the font to draw from to ES:BP. +; +SelectFont proc near + mov word ptr [FontPointer],bp ;save pointer + mov word ptr [FontPointer+2],es + ret +SelectFont endp +; +cseg ends + end start +``` 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, diff --git a/27-02.md b/27-02.md index b41128f..5890b4f 100644 --- a/27-02.md +++ b/27-02.md @@ -52,220 +52,222 @@ 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 - ; 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' - db 512 dup(0) - Stack ends +```nasm +; 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 +; 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' + db 512 dup(0) +Stack ends - SCREEN_WIDTH_IN_BYTES equ 80 - DISPLAY_MEMORY_SEGMENT equ 0a000h - SC_INDEX equ 3c4h ;Sequence Controller Index register - MAP_MASK equ 2 ;index of Map Mask register - GC_INDEX equ 03ceh ;Graphics Controller Index reg - GRAPHICS_MODE equ 5 ;index of Graphics Mode reg - BIT_MASKequ 8 ;index of Bit Mask reg +SCREEN_WIDTH_IN_BYTES equ 80 +DISPLAY_MEMORY_SEGMENT equ 0a000h +SC_INDEX equ 3c4h ;Sequence Controller Index register +MAP_MASK equ 2 ;index of Map Mask register +GC_INDEX equ 03ceh ;Graphics Controller Index reg +GRAPHICS_MODE equ 5 ;index of Graphics Mode reg +BIT_MASKequ 8 ;index of Bit Mask reg - Data segment para common ‘DATA' - ; - ; 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 - ; - AImage label byte - dw 13, 13 ;width, height in pixels - db 000h, 000h, 000h, 000h, 000h, 000h, 000h - db 009h, 099h, 099h, 099h, 099h, 099h, 000h - db 009h, 099h, 099h, 099h, 099h, 099h, 000h - db 009h, 099h, 099h, 0e9h, 099h, 099h, 000h - db 009h, 099h, 09eh, 0eeh, 099h, 099h, 000h - db 009h, 099h, 0eeh, 09eh, 0e9h, 099h, 000h - db 009h, 09eh, 0e9h, 099h, 0eeh, 099h, 000h - db 009h, 09eh, 0eeh, 0eeh, 0eeh, 099h, 000h - db 009h, 09eh, 0e9h, 099h, 0eeh, 099h, 000h - db 009h, 09eh, 0e9h, 099h, 0eeh, 099h, 000h - db 009h, 099h, 099h, 099h, 099h, 099h, 000h - db 009h, 099h, 099h, 099h, 099h, 099h, 000h - db 000h, 000h, 000h, 000h, 000h, 000h, 000h - Data ends +Data segment para common ‘DATA' +; +; 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 +; +AImage label byte + dw 13, 13 ;width, height in pixels + db 000h, 000h, 000h, 000h, 000h, 000h, 000h + db 009h, 099h, 099h, 099h, 099h, 099h, 000h + db 009h, 099h, 099h, 099h, 099h, 099h, 000h + db 009h, 099h, 099h, 0e9h, 099h, 099h, 000h + db 009h, 099h, 09eh, 0eeh, 099h, 099h, 000h + db 009h, 099h, 0eeh, 09eh, 0e9h, 099h, 000h + db 009h, 09eh, 0e9h, 099h, 0eeh, 099h, 000h + db 009h, 09eh, 0eeh, 0eeh, 0eeh, 099h, 000h + db 009h, 09eh, 0e9h, 099h, 0eeh, 099h, 000h + db 009h, 09eh, 0e9h, 099h, 0eeh, 099h, 000h + db 009h, 099h, 099h, 099h, 099h, 099h, 000h + db 009h, 099h, 099h, 099h, 099h, 099h, 000h + db 000h, 000h, 000h, 000h, 000h, 000h, 000h +Data ends - Code segment para public ‘CODE' - assume cs:Code, ds:Data - Start proc near - mov ax,Data - mov ds,ax - mov ax,10h - int 10h ;select video mode 10h (640x350) - ; - ; Prepare for animation. - ; - mov [CurrentX],0 - mov [CurrentY],200 - mov [RemainingLength],600 ;move 600 times - ; - ; 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. - ; - AnimationLoop: - mov bx,[CurrentX] - mov cx,[CurrentY] - mov si,offset AImage - call DrawFromChunkyBitmap ;draw the "A" image - inc [CurrentX] ;move one pixel to the right +Code segment para public ‘CODE' + assume cs:Code, ds:Data +Start proc near + mov ax,Data + mov ds,ax + mov ax,10h + int 10h ;select video mode 10h (640x350) +; +; Prepare for animation. +; + mov [CurrentX],0 + mov [CurrentY],200 + mov [RemainingLength],600 ;move 600 times +; +; 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. +; +AnimationLoop: + mov bx,[CurrentX] + mov cx,[CurrentY] + mov si,offset AImage + call DrawFromChunkyBitmap ;draw the "A" image + inc [CurrentX] ;move one pixel to the right - mov cx,0 ;delay so we don't move the - DelayLoop: ; image too fast; adjust as - ; needed - loop DelayLoop + mov cx,0 ;delay so we don't move the +DelayLoop: ; image too fast; adjust as + ; needed + loop DelayLoop - dec [RemainingLength] - jnz AnimationLoop - ; - ; Wait for a key before returning to text mode and ending. - ; - mov ah,01h - int 21h - mov ax,03h - int 10h - mov ah,4ch - int 21h - Start endp - ; - ; Draw an image stored in a chunky-bit map into planar VGA/EGA memory - ; at the specified location. - ; - ; Input: - ; BX = X screen location at which to draw the upper-left corner - ; of the image - ; CX = Y screen location at which to draw the upper-left corner - ; of the image - ; DS:SI = pointer to chunky image to draw, as follows: - ; word at 0: width of image, in pixels - ; word at 2: height of image, in pixels - ; byte at 4: msb/lsb = first & second chunky pixels, - ; repeating for the remainder of the scan line - ; of the image, then for all scan lines. Images - ; with odd widths have an unused null nibble - ; padding each scan line out to a byte width - ; - ; AX, BX, CX, DX, SI, DI, ES destroyed. - ; - DrawFromChunkyBitmap proc near - cld - ; - ; Select write mode 2. - ; - mov dx,GC_INDEX - mov al,GRAPHICS_MODE - out dx,al - inc dx - mov al,02h - out dx,al - ; - ; Enable writes to all 4 planes. - ; - mov dx,SC_INDEX - mov al,MAP_MASK - out dx,al - inc dx - mov al,0fh - out dx,al - ; - ; Point ES:DI to the display memory byte in which the first pixel - ; of the image goes, with AH set up as the bit mask to access that - ; pixel within the addressed byte. - ; - mov ax,SCREEN_WIDTH_IN_BYTES - mul cx ;offset of start of top scan line - mov di,ax - mov cl,bl - and cl,111b - mov ah,80h ;set AH to the bit mask for the - shr ah,cl ; initial pixel - shr bx,1 - shr bx,1 - shr bx,1 ;X in bytes - add di,bx ;offset of upper-left byte of image - mov bx,DISPLAY_MEMORY_SEGMENT - mov es,bx ;ES:DI points to the byte at which the - ; upper left of the image goes - ; - ; Get the width and height of the image. - ; - mov cx,[si] ;get the width - inc si - inc si - mov bx,[si] ;get the height - inc si - inc si - mov dx,GC_INDEX - mov al,BIT_MASK - out dx,al ;leave the GC Index register pointing - inc dx ; to the Bit Mask register - RowLoop: + dec [RemainingLength] + jnz AnimationLoop +; +; Wait for a key before returning to text mode and ending. +; + mov ah,01h + int 21h + mov ax,03h + int 10h + mov ah,4ch + int 21h +Start endp +; +; Draw an image stored in a chunky-bit map into planar VGA/EGA memory +; at the specified location. +; +; Input: +; BX = X screen location at which to draw the upper-left corner +; of the image +; CX = Y screen location at which to draw the upper-left corner +; of the image +; DS:SI = pointer to chunky image to draw, as follows: +; word at 0: width of image, in pixels +; word at 2: height of image, in pixels +; byte at 4: msb/lsb = first & second chunky pixels, +; repeating for the remainder of the scan line +; of the image, then for all scan lines. Images +; with odd widths have an unused null nibble +; padding each scan line out to a byte width +; +; AX, BX, CX, DX, SI, DI, ES destroyed. +; +DrawFromChunkyBitmap proc near + cld +; +; Select write mode 2. +; + mov dx,GC_INDEX + mov al,GRAPHICS_MODE + out dx,al + inc dx + mov al,02h + out dx,al +; +; Enable writes to all 4 planes. +; + mov dx,SC_INDEX + mov al,MAP_MASK + out dx,al + inc dx + mov al,0fh + out dx,al +; +; Point ES:DI to the display memory byte in which the first pixel +; of the image goes, with AH set up as the bit mask to access that +; pixel within the addressed byte. +; + mov ax,SCREEN_WIDTH_IN_BYTES + mul cx ;offset of start of top scan line + mov di,ax + mov cl,bl + and cl,111b + mov ah,80h ;set AH to the bit mask for the + shr ah,cl ; initial pixel + shr bx,1 + shr bx,1 + shr bx,1 ;X in bytes + add di,bx ;offset of upper-left byte of image + mov bx,DISPLAY_MEMORY_SEGMENT + mov es,bx ;ES:DI points to the byte at which the + ; upper left of the image goes +; +; Get the width and height of the image. +; + mov cx,[si] ;get the width + inc si + inc si + mov bx,[si] ;get the height + inc si + inc si + mov dx,GC_INDEX + mov al,BIT_MASK + out dx,al ;leave the GC Index register pointing + inc dx ; to the Bit Mask register +RowLoop: - push ax ;preserve the left column's bit mask - push cx ;preserve the width - push di ;preserve the destination offset + push ax ;preserve the left column's bit mask + push cx ;preserve the width + push di ;preserve the destination offset - ColumnLoop: - mov al,ah - out dx,al ;set the bit mask to draw this pixel - mov al,es:[di] ;load the latches - mov al,[si] ;get the next two chunky pixels - shr al,1 - shr al,1 - shr al,1 - shr al,1 ;move the first pixel into the lsb - stosb ;draw the first pixel - ror ah,1 ;move mask to next pixel position - jc CheckMorePixels ;is next pixel in the adjacent byte? - dec di ;no +ColumnLoop: + mov al,ah + out dx,al ;set the bit mask to draw this pixel + mov al,es:[di] ;load the latches + mov al,[si] ;get the next two chunky pixels + shr al,1 + shr al,1 + shr al,1 + shr al,1 ;move the first pixel into the lsb + stosb ;draw the first pixel + ror ah,1 ;move mask to next pixel position + jc CheckMorePixels ;is next pixel in the adjacent byte? + dec di ;no - CheckMorePixels: - dec cx ;see if there are any more pixels - jz AdvanceToNextScanLine ; across in image - mov al,ah - out dx,al ;set the bit mask to draw this pixel - mov al,es:[di] ;load the latches - lodsb ;get the same two chunky pixels again - ; and advance pointer to the next - ; two pixels - stosb ;draw the second of the two pixels - ror ah,1 ;move mask to next pixel position - jc CheckMorePixels2 ;is next pixel in the adjacent byte? - dec di ;no +CheckMorePixels: + dec cx ;see if there are any more pixels + jz AdvanceToNextScanLine ; across in image + mov al,ah + out dx,al ;set the bit mask to draw this pixel + mov al,es:[di] ;load the latches + lodsb ;get the same two chunky pixels again + ; and advance pointer to the next + ; two pixels + stosb ;draw the second of the two pixels + ror ah,1 ;move mask to next pixel position + jc CheckMorePixels2 ;is next pixel in the adjacent byte? + dec di ;no - CheckMorePixels2: - loop ColumnLoop ;see if there are any more pixels - ; across in the image - jmp short CheckMoreScanLines +CheckMorePixels2: + loop ColumnLoop ;see if there are any more pixels + ; across in the image + jmp short CheckMoreScanLines - AdvanceToNextScanLine: - inc si ;advance to the start of the next - ; scan line in the image +AdvanceToNextScanLine: + inc si ;advance to the start of the next + ; scan line in the 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 - add di,SCREEN_WIDTH_IN_BYTES - ;point to the start of the next scan - ; line of the image - dec bx ;see if there are any more scan lines - jnz RowLoop ; in the image - ret - DrawFromChunkyBitmap endp - Code ends - end Start +CheckMoreScanLines: + pop di ;get back the destination offset + pop cx ;get back the width + 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 + dec bx ;see if there are any more scan lines + jnz RowLoop ; in the image + ret +DrawFromChunkyBitmap endp +Code ends + end Start +``` diff --git a/27-03.md b/27-03.md index c119d86..7a42738 100644 --- a/27-03.md +++ b/27-03.md @@ -53,329 +53,331 @@ the CPU byte in write mode 2 to select the color in which to draw. **LISTING 27.2 L27-2.ASM** - ; Program to illustrate one use of write mode 2 of the VGA and EGA by - ; drawing lines in color patterns. - ; - ; Assemble with MASM or TASM - ; - ; By Michael Abrash - ; - Stack segment para stack ‘STACK' - db 512 dup(0) - Stack ends +```nasm +; Program to illustrate one use of write mode 2 of the VGA and EGA by +; drawing lines in color patterns. +; +; Assemble with MASM or TASM +; +; By Michael Abrash +; +Stack segment para stack ‘STACK' + db 512 dup(0) +Stack ends - SCREEN_WIDTH_IN_BYTES equ 80 - GRAPHICS_SEGMENT equ 0a000h ;mode 10 bit-map segment - SC_INDEX equ 3c4h ;Sequence Controller Index register - MAP_MASK equ 2 ;index of Map Mask register - GC_INDEX equ 03ceh ;Graphics Controller Index reg - GRAPHICS_MODE equ 5 ;index of Graphics Mode reg - BIT_MASK equ 8 ;index of Bit Mask reg +SCREEN_WIDTH_IN_BYTES equ 80 +GRAPHICS_SEGMENT equ 0a000h ;mode 10 bit-map segment +SC_INDEX equ 3c4h ;Sequence Controller Index register +MAP_MASK equ 2 ;index of Map Mask register +GC_INDEX equ 03ceh ;Graphics Controller Index reg +GRAPHICS_MODE equ 5 ;index of Graphics Mode reg +BIT_MASK equ 8 ;index of Bit Mask reg - 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 - Pattern1 db 6 - db 2, 2, 2, 10, 10, 10 - Pattern2 db 8 - db 15, 15, 15, 0, 0, 15, 0, 0 - Pattern3 db 9 - db 1, 1, 1, 2, 2, 2, 4, 4, 4 - Data ends +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 +Pattern1 db 6 + db 2, 2, 2, 10, 10, 10 +Pattern2 db 8 + db 15, 15, 15, 0, 0, 15, 0, 0 +Pattern3 db 9 + db 1, 1, 1, 2, 2, 2, 4, 4, 4 +Data ends - Code segment para public ‘CODE' - assume cs:Code, ds:Data - Start proc near - mov ax,Data - mov ds,ax - mov ax,10h - int 10h ;select video mode 10h (640x350) - ; - ; Draw 8 radial lines in upper-left quadrant in pattern 0. - ; - mov bx,0 - mov cx,0 - mov si,offset Pattern0 - call QuadrantUp - ; - ; Draw 8 radial lines in upper-right quadrant in pattern 1. - ; - mov bx,320 - mov cx,0 - mov si,offset Pattern1 - call QuadrantUp - ; - ; Draw 8 radial lines in lower-left quadrant in pattern 2. - ; - mov bx,0 - mov cx,175 - mov si,offset Pattern2 - call QuadrantUp - ; - ; Draw 8 radial lines in lower-right quadrant in pattern 3. - ; - mov bx,320 - mov cx,175 - mov si,offset Pattern3 - call QuadrantUp - ; - ; Wait for a key before returning to text mode and ending. - ; - mov ah,01h - int 21h - mov ax,03h - int 10h - mov ah,4ch - int 21h - ; - ; Draws 8 radial lines with specified pattern in specified mode 10h - ; quadrant. - ; - ; Input: - ; BX = X coordinate of upper left corner of quadrant - ; CX = Y coordinate of upper left corner of quadrant - ; SI = pointer to pattern, in following form: - ; Byte 0: Length of pattern - ; Byte 1: Start of pattern, one color per byte - ; - ; AX, BX, CX, DX destroyed - ; - QuadrantUp proc near - add bx,160 - add cx,87 ;point to the center of the quadrant - mov ax,0 - mov dx,160 - call LineUp ;draw horizontal line to right edge - mov ax,1 - mov dx,88 - call LineUp ;draw diagonal line to upper right - mov ax,2 - mov dx,88 - call LineUp ;draw vertical line to top edge - mov ax,3 - mov dx,88 - call LineUp ;draw diagonal line to upper left - mov ax,4 - mov dx,161 - call LineUp ;draw horizontal line to left edge - mov ax,5 - mov dx,88 - call LineUp ;draw diagonal line to lower left - mov ax,6 - mov dx,88 - call LineUp ;draw vertical line to bottom edge - mov ax,7 - mov dx,88 - call LineUp ;draw diagonal line to bottom right - ret - QuadrantUp endp - ; - ; Draws a horizontal, vertical, or diagonal line (one of the eight - ; possible radial lines) of the specified length from the specified - ; starting point. - ; - ; Input: - ; AX = line direction, as follows: - ; 3 2 1 - ; 4 * 0 - ; 5 6 7 - ; BX = X coordinate of starting point - ; CX = Y coordinate of starting point - ; DX = length of line (number of pixels drawn) - ; - ; All registers preserved. - ; - ; Table of vectors to routines for each of the 8 possible lines. - ; - LineUpVectors label word - dw LineUp0, LineUp1, LineUp2, LineUp3 - dw LineUp4, LineUp5, LineUp6, LineUp7 +Code segment para public ‘CODE' + assume cs:Code, ds:Data +Start proc near + mov ax,Data + mov ds,ax + mov ax,10h + int 10h ;select video mode 10h (640x350) +; +; Draw 8 radial lines in upper-left quadrant in pattern 0. +; + mov bx,0 + mov cx,0 + mov si,offset Pattern0 + call QuadrantUp +; +; Draw 8 radial lines in upper-right quadrant in pattern 1. +; + mov bx,320 + mov cx,0 + mov si,offset Pattern1 + call QuadrantUp +; +; Draw 8 radial lines in lower-left quadrant in pattern 2. +; + mov bx,0 + mov cx,175 + mov si,offset Pattern2 + call QuadrantUp +; +; Draw 8 radial lines in lower-right quadrant in pattern 3. +; + mov bx,320 + mov cx,175 + mov si,offset Pattern3 + call QuadrantUp +; +; Wait for a key before returning to text mode and ending. +; + mov ah,01h + int 21h + mov ax,03h + int 10h + mov ah,4ch + int 21h +; +; Draws 8 radial lines with specified pattern in specified mode 10h +; quadrant. +; +; Input: +; BX = X coordinate of upper left corner of quadrant +; CX = Y coordinate of upper left corner of quadrant +; SI = pointer to pattern, in following form: +; Byte 0: Length of pattern +; Byte 1: Start of pattern, one color per byte +; +; AX, BX, CX, DX destroyed +; +QuadrantUp proc near + add bx,160 + add cx,87 ;point to the center of the quadrant + mov ax,0 + mov dx,160 + call LineUp ;draw horizontal line to right edge + mov ax,1 + mov dx,88 + call LineUp ;draw diagonal line to upper right + mov ax,2 + mov dx,88 + call LineUp ;draw vertical line to top edge + mov ax,3 + mov dx,88 + call LineUp ;draw diagonal line to upper left + mov ax,4 + mov dx,161 + call LineUp ;draw horizontal line to left edge + mov ax,5 + mov dx,88 + call LineUp ;draw diagonal line to lower left + mov ax,6 + mov dx,88 + call LineUp ;draw vertical line to bottom edge + mov ax,7 + mov dx,88 + call LineUp ;draw diagonal line to bottom right + ret +QuadrantUp endp +; +; Draws a horizontal, vertical, or diagonal line (one of the eight +; possible radial lines) of the specified length from the specified +; starting point. +; +; Input: +; AX = line direction, as follows: +; 3 2 1 +; 4 * 0 +; 5 6 7 +; BX = X coordinate of starting point +; CX = Y coordinate of starting point +; DX = length of line (number of pixels drawn) +; +; All registers preserved. +; +; Table of vectors to routines for each of the 8 possible lines. +; +LineUpVectors label word + dw LineUp0, LineUp1, LineUp2, LineUp3 + dw LineUp4, LineUp5, LineUp6, LineUp7 - ; - ; Macro to draw horizontal, vertical, or diagonal line. - ; - ; Input: - ; XParm = 1 to draw right, -1 to draw left, 0 to not move horz. - ; YParm = 1 to draw up, -1 to draw down, 0 to not move vert. - ; BX = X start location - ; CX = Y start location - ; DX = number of pixels to draw - ; DS:SI = line pattern - ; - MLineUp macro XParm, YParm - local LineUpLoop, CheckMoreLine - mov di,si ;set aside start offset of pattern - lodsb ;get length of pattern - mov ah,al +; +; Macro to draw horizontal, vertical, or diagonal line. +; +; Input: +; XParm = 1 to draw right, -1 to draw left, 0 to not move horz. +; YParm = 1 to draw up, -1 to draw down, 0 to not move vert. +; BX = X start location +; CX = Y start location +; DX = number of pixels to draw +; DS:SI = line pattern +; +MLineUp macro XParm, YParm + local LineUpLoop, CheckMoreLine + mov di,si ;set aside start offset of pattern + lodsb ;get length of pattern + mov ah,al - LineUpLoop: - lodsb ;get color of this pixel... - call DotUpInColor ;...and draw it - if XParm EQ 1 - inc bx - endif - if XParm EQ -1 - dec bx - endif - if YParm EQ 1 - inc cx - endif - if YParm EQ -1 - dec cx - endif - dec ah ;at end of pattern? - jnz CheckMoreLine - mov si,di ;get back start of pattern - lodsb - mov ah,al ;reset pattern count +LineUpLoop: + lodsb ;get color of this pixel... + call DotUpInColor ;...and draw it +if XParm EQ 1 + inc bx +endif +if XParm EQ -1 + dec bx +endif +if YParm EQ 1 + inc cx +endif +if YParm EQ -1 + dec cx +endif + dec ah ;at end of pattern? + jnz CheckMoreLine + mov si,di ;get back start of pattern + lodsb + mov ah,al ;reset pattern count - CheckMoreLine: - dec dx - jnz LineUpLoop - jmp LineUpEnd - endm +CheckMoreLine: + dec dx + jnz LineUpLoop + jmp LineUpEnd + endm - LineUp proc near - push ax - push bx - push cx - push dx - push si - push di - push es +LineUp proc near + push ax + push bx + push cx + push dx + push si + push di + push es - mov di,ax + mov di,ax - mov ax,GRAPHICS_SEGMENT - mov es,ax + mov ax,GRAPHICS_SEGMENT + mov es,ax - push dx ;save line length - ; - ; Enable writes to all planes. - ; - mov dx,SC_INDEX - mov al,MAP_MASK - out dx,al - inc dx - mov al,0fh - out dx,al - ; - ; Select write mode 2. - ; - mov dx,GC_INDEX - mov al,GRAPHICS_MODE - out dx,al - inc dx - mov al,02h - out dx,al - ; - ; Vector to proper routine. - ; - pop dx ;get back line length + push dx ;save line length +; +; Enable writes to all planes. +; + mov dx,SC_INDEX + mov al,MAP_MASK + out dx,al + inc dx + mov al,0fh + out dx,al +; +; Select write mode 2. +; + mov dx,GC_INDEX + mov al,GRAPHICS_MODE + out dx,al + inc dx + mov al,02h + out dx,al +; +; Vector to proper routine. +; + pop dx ;get back line length - shl di,1 - jmp cs:[LineUpVectors+di] - ; - ; Horizontal line to right. - ; - LineUp0: - MLineUp 1, 0 - ; - ; Diagonal line to upper right. - ; - LineUp1: - MLineUp 1, -1 - ; - ; Vertical line to top. - ; - LineUp2: - MLineUp 0, -1 - ; - ; Diagonal line to upper left. - ; - LineUp3: - MLineUp -1, -1 - ; - ; Horizontal line to left. - ; - LineUp4: - MLineUp -1, 0 - ; - ; Diagonal line to bottom left. - ; - LineUp5: - MLineUp -1, 1 - ; - ; Vertical line to bottom. - ; - LineUp6: - MLineUp 0, 1 - ; - ; Diagonal line to bottom right. - ; - LineUp7: - MLineUp 1, 1 + shl di,1 + jmp cs:[LineUpVectors+di] +; +; Horizontal line to right. +; +LineUp0: + MLineUp 1, 0 +; +; Diagonal line to upper right. +; +LineUp1: + MLineUp 1, -1 +; +; Vertical line to top. +; +LineUp2: + MLineUp 0, -1 +; +; Diagonal line to upper left. +; +LineUp3: + MLineUp -1, -1 +; +; Horizontal line to left. +; +LineUp4: + MLineUp -1, 0 +; +; Diagonal line to bottom left. +; +LineUp5: + MLineUp -1, 1 +; +; Vertical line to bottom. +; +LineUp6: + MLineUp 0, 1 +; +; Diagonal line to bottom right. +; +LineUp7: + MLineUp 1, 1 - LineUpEnd: - pop es - pop di - pop si - pop dx - pop cx - pop bx - pop ax - ret - LineUp endp - ; - ; Draws a dot in the specified color at the specified location. - ; Assumes that the VGA is in write mode 2 with writes to all planes - ; enabled and that ES points to display memory. - ; - ; Input: - ; AL = dot color - ; BX = X coordinate of dot - ; CX = Y coordinate of dot - ; ES = display memory segment - ; - ; All registers preserved. - ; - DotUpInColor proc near - push bx - push cx - push dx - push di - ; - ; Point ES:DI to the display memory byte in which the pixel goes, with - ; the bit mask set up to access that pixel within the addressed byte. - ; - push ax ;preserve dot color - mov ax,SCREEN_WIDTH_IN_BYTES - mul cx ;offset of start of top scan line - mov di,ax - mov cl,bl - and cl,111b - mov dx,GC_INDEX - mov al,BIT_MASK - out dx,al - inc dx - mov al,80h - shr al,cl - out dx,al ;set the bit mask for the pixel - shr bx,1 - shr bx,1 - shr bx,1 ;X in bytes - add di,bx ;offset of byte pixel is in - mov al,es:[di] ;load latches - pop ax ;get back dot color - stosb ;write dot in desired color +LineUpEnd: + pop es + pop di + pop si + pop dx + pop cx + pop bx + pop ax + ret +LineUp endp +; +; Draws a dot in the specified color at the specified location. +; Assumes that the VGA is in write mode 2 with writes to all planes +; enabled and that ES points to display memory. +; +; Input: +; AL = dot color +; BX = X coordinate of dot +; CX = Y coordinate of dot +; ES = display memory segment +; +; All registers preserved. +; +DotUpInColor proc near + push bx + push cx + push dx + push di +; +; Point ES:DI to the display memory byte in which the pixel goes, with +; the bit mask set up to access that pixel within the addressed byte. +; + push ax ;preserve dot color + mov ax,SCREEN_WIDTH_IN_BYTES + mul cx ;offset of start of top scan line + mov di,ax + mov cl,bl + and cl,111b + mov dx,GC_INDEX + mov al,BIT_MASK + out dx,al + inc dx + mov al,80h + shr al,cl + out dx,al ;set the bit mask for the pixel + shr bx,1 + shr bx,1 + shr bx,1 ;X in bytes + add di,bx ;offset of byte pixel is in + mov al,es:[di] ;load latches + pop ax ;get back dot color + stosb ;write dot in desired color - pop di - pop dx - pop cx - pop bx - ret - DotUpInColor endp - Start endp - Code ends - end Start + pop di + pop dx + pop cx + pop bx + ret +DotUpInColor endp +Start endp +Code ends + end Start +``` diff --git a/27-05.md b/27-05.md index 7836959..ca2f3fa 100644 --- a/27-05.md +++ b/27-05.md @@ -53,194 +53,196 @@ rewarding!) VGA to cover. **LISTING 27.3 L27-3.ASM** - ; Program to illustrate flipping from bit-mapped graphics mode to - ; text mode and back without losing any of the graphics bit-map. - ; - ; Assemble with MASM or TASM - ; - ; By Michael Abrash - ; - Stack segment para stack ‘STACK' - db 512 dup(0) - Stack ends +```nasm +; Program to illustrate flipping from bit-mapped graphics mode to +; text mode and back without losing any of the graphics bit-map. +; +; Assemble with MASM or TASM +; +; By Michael Abrash +; +Stack segment para stack ‘STACK' + db 512 dup(0) +Stack ends - GRAPHICS_SEGMENT equ 0a000h ;mode 10 bit-map segment - TEXT_SEGMENT equ 0b800h ;mode 3 bit-map segment - SC_INDEX equ 3c4h ;Sequence Controller Index register - MAP_MASK equ 2 ;index of Map Mask register - GC_INDEX equ 3ceh ;Graphics Controller Index register - READ_MAP equ 4 ;index of Read Map register +GRAPHICS_SEGMENT equ 0a000h ;mode 10 bit-map segment +TEXT_SEGMENT equ 0b800h ;mode 3 bit-map segment +SC_INDEX equ 3c4h ;Sequence Controller Index register +MAP_MASK equ 2 ;index of Map Mask register +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, ‘$' +GStrikeAnyKeyMsg0 label byte + 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, ‘$' +GStrikeAnyKeyMsg1 label byte + 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, ‘$' +TStrikeAnyKeyMsg label byte + 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 - CharAttSave db 4000 dup (?) ;save area for memory wiped - ; out by character/attribute - ; data in text mode - Data ends +Plane2Save db 2000h dup (?) ;save area for plane 2 data + ; where font gets loaded +CharAttSave db 4000 dup (?) ;save area for memory wiped + ; out by character/attribute + ; data in text mode +Data ends - Code segment para public ‘CODE' - assume cs:Code, ds:Data - Start proc near - mov ax,10h - int 10h ;select video mode 10h (640x350) - ; - ; Fill the graphics bit-map with a colored pattern. - ; - cld - mov ax,GRAPHICS_SEGMENT - mov es,ax - mov ah,3 ;initial fill pattern - mov cx,4 ;four planes to fill - mov dx,SC_INDEX - mov al,MAP_MASK - out dx,al ;leave the SC Index pointing to the - inc dx ; Map Mask register +Code segment para public ‘CODE' + assume cs:Code, ds:Data +Start proc near + mov ax,10h + int 10h ;select video mode 10h (640x350) +; +; Fill the graphics bit-map with a colored pattern. +; + cld + mov ax,GRAPHICS_SEGMENT + mov es,ax + mov ah,3 ;initial fill pattern + mov cx,4 ;four planes to fill + mov dx,SC_INDEX + mov al,MAP_MASK + out dx,al ;leave the SC Index pointing to the + inc dx ; Map Mask register - FillBitMap: - mov al,10h - shr al,cl ;generate map mask for this plane - out dx,al ;set map mask for this plane - sub di,di ;start at offset 0 - mov al,ah ;get the fill pattern - push cx ;preserve plane count - mov cx,8000h ;fill 32K words - rep stosw ;do fill for this plane - pop cx ;get back plane count - shl ah,1 - shl ah,1 - loop FillBitMap - ; - ; Put up "strike any key" message. - ; - mov ax,Data - mov ds,ax - mov dx,offset GStrikeAnyKeyMsg0 - mov ah,9 - int 21h - ; - ; Wait for a key. - ; - mov ah,01h - int 21h - ; - ; Save the 8K of plane 2 that will be used by the font. - ; - mov dx,GC_INDEX - mov al,READ_MAP - out dx,al - inc dx - mov al,2 - out dx,al ;set up to read from plane 2 - mov ax,Data - mov es,ax - mov ax,GRAPHICS_SEGMENT - mov ds,ax - sub si,si - mov di,offset Plane2Save - mov cx,2000h/2 ;save 8K (length of default font) - rep movsw - ; - ; Go to text mode without clearing display memory. - ; - mov ax,083h - int 10h - ; - ; Save the text mode bit-map. - ; - mov ax,Data - mov es,ax - mov ax,TEXT_SEGMENT - mov ds,ax - sub si,si - mov di,offset CharAttSave - 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" - ; message. - ; - mov ax,TEXT_SEGMENT - mov es,ax - sub di,di - mov al,‘.' ;fill character - mov ah,7 ;fill attribute - mov cx,4000/2 ;length of one text screen in words - rep stosw - mov ax,Data - mov ds,ax - mov dx,offset TStrikeAnyKeyMsg - mov ah,9 - int 21h - ; - ; Wait for a key. - ; - mov ah,01h - int 21h - ; - ; Restore the text mode screen to the state it was in on entering - ; text mode. - ; - mov ax,Data - mov ds,ax - mov ax,TEXT_SEGMENT - mov es,ax - mov si,offset CharAttSave - sub di,di - mov cx,4000/2 ;length of one text screen in words - rep movsw - ; - ; Return to mode 10h without clearing display memory. - ; - mov ax,90h - int 10h - ; - ; Restore the portion of plane 2 that was wiped out by the font. - ; - mov dx,SC_INDEX - mov al,MAP_MASK - out dx,al - inc dx - mov al,4 - out dx,al ;set up to write to plane 2 - mov ax,Data - mov ds,ax - mov ax,GRAPHICS_SEGMENT - mov es,ax - mov si,offset Plane2Save - sub di,di - mov cx,2000h/2 ;restore 8K (length of default font) - rep movsw - ; - ; Put up "strike any key" message. - ; - mov ax,Data - mov ds,ax - mov dx,offset GStrikeAnyKeyMsg1 - mov ah,9 - int 21h - ; - ; Wait for a key before returning to text mode and ending. - ; - mov ah,01h - int 21h - mov ax,03h - int 10h - mov ah,4ch - int 21h - Start endp - Code ends - end Start +FillBitMap: + mov al,10h + shr al,cl ;generate map mask for this plane + out dx,al ;set map mask for this plane + sub di,di ;start at offset 0 + mov al,ah ;get the fill pattern + push cx ;preserve plane count + mov cx,8000h ;fill 32K words + rep stosw ;do fill for this plane + pop cx ;get back plane count + shl ah,1 + shl ah,1 + loop FillBitMap +; +; Put up "strike any key" message. +; + mov ax,Data + mov ds,ax + mov dx,offset GStrikeAnyKeyMsg0 + mov ah,9 + int 21h +; +; Wait for a key. +; + mov ah,01h + int 21h +; +; Save the 8K of plane 2 that will be used by the font. +; + mov dx,GC_INDEX + mov al,READ_MAP + out dx,al + inc dx + mov al,2 + out dx,al ;set up to read from plane 2 + mov ax,Data + mov es,ax + mov ax,GRAPHICS_SEGMENT + mov ds,ax + sub si,si + mov di,offset Plane2Save + mov cx,2000h/2 ;save 8K (length of default font) + rep movsw +; +; Go to text mode without clearing display memory. +; + mov ax,083h + int 10h +; +; Save the text mode bit-map. +; + mov ax,Data + mov es,ax + mov ax,TEXT_SEGMENT + mov ds,ax + sub si,si + mov di,offset CharAttSave + 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" +; message. +; + mov ax,TEXT_SEGMENT + mov es,ax + sub di,di + mov al,‘.' ;fill character + mov ah,7 ;fill attribute + mov cx,4000/2 ;length of one text screen in words + rep stosw + mov ax,Data + mov ds,ax + mov dx,offset TStrikeAnyKeyMsg + mov ah,9 + int 21h +; +; Wait for a key. +; + mov ah,01h + int 21h +; +; Restore the text mode screen to the state it was in on entering +; text mode. +; + mov ax,Data + mov ds,ax + mov ax,TEXT_SEGMENT + mov es,ax + mov si,offset CharAttSave + sub di,di + mov cx,4000/2 ;length of one text screen in words + rep movsw +; +; Return to mode 10h without clearing display memory. +; + mov ax,90h + int 10h +; +; Restore the portion of plane 2 that was wiped out by the font. +; + mov dx,SC_INDEX + mov al,MAP_MASK + out dx,al + inc dx + mov al,4 + out dx,al ;set up to write to plane 2 + mov ax,Data + mov ds,ax + mov ax,GRAPHICS_SEGMENT + mov es,ax + mov si,offset Plane2Save + sub di,di + mov cx,2000h/2 ;restore 8K (length of default font) + rep movsw +; +; Put up "strike any key" message. +; + mov ax,Data + mov ds,ax + mov dx,offset GStrikeAnyKeyMsg1 + mov ah,9 + int 21h +; +; Wait for a key before returning to text mode and ending. +; + mov ah,01h + int 21h + mov ax,03h + int 10h + mov ah,4ch + int 21h +Start endp +Code ends + end Start +``` diff --git a/28-02.md b/28-02.md index 9f7082e..fda60c8 100644 --- a/28-02.md +++ b/28-02.md @@ -12,238 +12,240 @@ pages: 526-530 **LISTING 28.1 L28-1.ASM** - ; Program to illustrate the use of the Read Map register in read mode 0. - ; Animates by copying a 16-color image from VGA memory to system memory, - ; one plane at a time, then copying the image back to a new location - ; in VGA memory. - ; - ; By Michael Abrash - ; - stacksegmentword stack 'STACK' - db512 dup (?) - stackends - ; - datasegment word 'DATA' - IMAGE_WIDTHEQU 4 ;in bytes - IMAGE_HEIGHT EQU 32 ;in pixels - LEFT_BOUND EQU 10 ;in bytes - RIGHT_BOUND EQU 66 ;in bytes - VGA_SEGMENT EQU 0a000h - SCREEN_WIDTH EQU 80 ;in bytes - SC_INDEX EQU 3c4h ;Sequence Controller Index register - GC_INDEX EQU 3ceh ;Graphics Controller Index register - MAP_MASK EQU 2 ;Map Mask register index in SC - READ_MAP EQU 4 ;Read Map register index in GC - ; - ; Base pattern for 16-color image. - ; - PatternPlane0 label byte - db 32 dup (0ffh,0ffh,0,0) - PatternPlane1 labelbyte - db 32 dup (0ffh,0,0ffh,0) - PatternPlane2 labelbyte - db 32 dup (0f0h,0f0h,0f0h,0f0h) - PatternPlane3 labelbyte - db 32 dup (0cch,0cch,0cch,0cch) - ; - ; Temporary storage for 16-color image during animation. - ; - ImagePlane0 db 32*4 dup (?) - ImagePlane1 db 32*4 dup (?) - ImagePlane2 db 32*4 dup (?) - ImagePlane3 db 32*4 dup (?) - ; - ; Current image location & direction. - ; - ImageX dw 40 ;in bytes - ImageY dw 100 ;in pixels - ImageXDirection dw 1 ;in bytes - dataends - ; - code segment word 'CODE' - assume cs:code,ds:data - Start proc near - cld - mov ax,data - mov ds,ax - ; - ; Select graphics mode 10h. - ; - mov ax,10h - int 10h - ; - ; Draw the initial image. - ; - mov si,offset PatternPlane0 - call DrawImage - ; - ; Loop to animate by copying the image from VGA memory to system memory, - ; erasing the image, and copying the image from system memory to a new - ; location in VGA memory. Ends when a key is hit. - ; - AnimateLoop: - ; - ; Copy the image from VGA memory to system memory. - ; - mov di,offset ImagePlane0 - call GetImage - ; - ; Clear the image from VGA memory. - ; - call EraseImage - ; - ; Advance the image X coordinate, reversing direction if either edge - ; of the screen has been reached. - ; - mov ax,[ImageX] - cmp ax,LEFT_BOUND - jz ReverseDirection - cmp ax,RIGHT_BOUND - jnz SetNewX - ReverseDirection: - neg [ImageXDirection] - SetNewX: - add ax,[ImageXDirection] - mov [ImageX],ax - ; - ; Draw the image by copying it from system memory to VGA memory. - ; - mov si,offset ImagePlane0 - call DrawImage - ; - ; Slow things down a bit for visibility (adjust as needed). - ; - mov cx,0 - DelayLoop: - loop DelayLoop - ; - ; See if a key has been hit, ending the program. - ; - mov ah,1 - int 16h - jz AnimateLoop - ; - ; Clear the key, return to text mode, and return to DOS. - ; - sub ah,ah - int 16h - mov ax,3 - int 10h - mov ah,4ch - int 21h - Startendp - ; - ; Draws the image at offset DS:SI to the current image location in - ; VGA memory. - ; - DrawImageprocnear - mov ax,VGA_SEGMENT - mov es,ax - call GetImageOffset ;ES:DI is the destination address for the - ; image in VGA memory - mov dx,SC_INDEX - mov al,1 ;do plane 0 first - DrawImagePlaneLoop: - push di ;image is drawn at the same offset in - ; each plane - push ax ;preserve plane select - mov al,MAP_MASK ;Map Mask index - out dx,al ;point SC Index to the Map Mask register - pop ax ;get back plane select - inc dx ;point to SC index register - out dx,al ;set up the Map Mask to allow writes to - ; the plane of interest - dec dx ;point back to SC Data register - mov bx,IMAGE_HEIGHT ;# of scan lines in image - DrawImageLoop: - mov cx,IMAGE_WIDTH ;# of bytes across image - rep movsb - add di,SCREEN_WIDTH-IMAGE_WIDTH - ;point to next scan line of image - dec bx ;any more scan lines? - jnz DrawImageLoop - pop di ;get back image start offset in VGA memory - shl al,1 ;Map Mask setting for next plane - cmp al,10h ;have we done all four planes? - jnz DrawImagePlaneLoop - ret - DrawImageendp - ; - ; Copies the image from its current location in VGA memory into the - ; buffer at DS:DI. - ; - GetImage proc near - mov si,di ;move destination offset into SI - call GetImageOffset ;DI is offset of image in VGA memory - xchg si,di ;SI is offset of image, DI is destination offset - push ds - pop es ;ES:DI is destination - mov ax,VGA_SEGMENT - mov ds,ax ;DS:SI is source - ; - mov dx,GC_INDEX - sub al,al;do plane 0 first - GetImagePlaneLoop: - push si ;image comes from same offset in each plane - push ax ;preserve plane select - mov al,READ_MAP ;Read Map index - out dx,al ;point GC Index to Read Map register - pop ax ;get back plane select - inc dx ;point to GC Index register - out dx,al ;set up the Read Map to select reads from - ; the plane of interest - dec dx ;point back to GC data register - mov bx,IMAGE_HEIGHT ;# of scan lines in image - GetImageLoop: - mov cx,IMAGE_WIDTH ;# of bytes across image - rep movsb - add si,SCREEN_WIDTH-IMAGE_WIDTH - ;point to next scan line of image - dec bx ;any more scan lines? - jnz GetImageLoop - pop si ;get back image start offset - inc al ;Read Map setting for next plane - cmp al,4 ;have we done all four planes? - jnz GetImagePlaneLoop - push es - pop ds ;restore original DS - ret - GetImageendp - ; - ; Erases the image at its current location. - ; - EraseImage proc near - mov dx,SC_INDEX - mov al,MAP_MASK - out dx,al ;point SC Index to the Map Mask register - inc dx ;point to SC Data register - mov al,0fh - out dx,al ;set up the Map Mask to allow writes to go to - ; all 4 planes - mov ax,VGA_SEGMENT - mov es,ax - call GetImageOffset ;ES:DI points to the start address - ; of the image - sub al,al ;erase with zeros - mov bx,IMAGE_HEIGHT ;# of scan lines in image - EraseImageLoop: - mov cx,IMAGE_WIDTH ;# of bytes across image - rep stosb - add di,SCREEN_WIDTH-IMAGE_WIDTH - ;point to next scan line of image - dec bx ;any more scan lines? - jnz EraseImageLoop - ret - EraseImage endp - ; - ; Returns the current offset of the image in the VGA segment in DI. - ; - GetImageOffset proc near - mov ax,SCREEN_WIDTH - mul [ImageY] - add ax,[ImageX] - mov di,ax - ret - GetImageOffset endp - code ends - end Start +```nasm +; Program to illustrate the use of the Read Map register in read mode 0. +; Animates by copying a 16-color image from VGA memory to system memory, +; one plane at a time, then copying the image back to a new location +; in VGA memory. +; +; By Michael Abrash +; +stacksegmentword stack 'STACK' +db512 dup (?) +stackends +; +datasegment word 'DATA' +IMAGE_WIDTHEQU 4 ;in bytes +IMAGE_HEIGHT EQU 32 ;in pixels +LEFT_BOUND EQU 10 ;in bytes +RIGHT_BOUND EQU 66 ;in bytes +VGA_SEGMENT EQU 0a000h +SCREEN_WIDTH EQU 80 ;in bytes +SC_INDEX EQU 3c4h ;Sequence Controller Index register +GC_INDEX EQU 3ceh ;Graphics Controller Index register +MAP_MASK EQU 2 ;Map Mask register index in SC +READ_MAP EQU 4 ;Read Map register index in GC +; + ; Base pattern for 16-color image. +; +PatternPlane0 label byte + db 32 dup (0ffh,0ffh,0,0) +PatternPlane1 labelbyte + db 32 dup (0ffh,0,0ffh,0) +PatternPlane2 labelbyte + db 32 dup (0f0h,0f0h,0f0h,0f0h) +PatternPlane3 labelbyte + db 32 dup (0cch,0cch,0cch,0cch) +; +; Temporary storage for 16-color image during animation. +; +ImagePlane0 db 32*4 dup (?) +ImagePlane1 db 32*4 dup (?) +ImagePlane2 db 32*4 dup (?) +ImagePlane3 db 32*4 dup (?) +; +; Current image location & direction. +; +ImageX dw 40 ;in bytes +ImageY dw 100 ;in pixels +ImageXDirection dw 1 ;in bytes +dataends +; +code segment word 'CODE' + assume cs:code,ds:data +Start proc near + cld + mov ax,data + mov ds,ax +; +; Select graphics mode 10h. +; + mov ax,10h + int 10h +; +; Draw the initial image. +; + mov si,offset PatternPlane0 + call DrawImage +; +; Loop to animate by copying the image from VGA memory to system memory, +; erasing the image, and copying the image from system memory to a new +; location in VGA memory. Ends when a key is hit. +; +AnimateLoop: +; +; Copy the image from VGA memory to system memory. +; + mov di,offset ImagePlane0 + call GetImage +; +; Clear the image from VGA memory. +; + call EraseImage +; +; Advance the image X coordinate, reversing direction if either edge +; of the screen has been reached. +; + mov ax,[ImageX] + cmp ax,LEFT_BOUND + jz ReverseDirection + cmp ax,RIGHT_BOUND + jnz SetNewX +ReverseDirection: + neg [ImageXDirection] +SetNewX: + add ax,[ImageXDirection] + mov [ImageX],ax +; +; Draw the image by copying it from system memory to VGA memory. +; + mov si,offset ImagePlane0 + call DrawImage +; +; Slow things down a bit for visibility (adjust as needed). +; + mov cx,0 +DelayLoop: + loop DelayLoop +; +; See if a key has been hit, ending the program. +; + mov ah,1 + int 16h + jz AnimateLoop +; +; Clear the key, return to text mode, and return to DOS. +; + sub ah,ah + int 16h + mov ax,3 + int 10h + mov ah,4ch + int 21h +Startendp +; +; Draws the image at offset DS:SI to the current image location in +; VGA memory. +; +DrawImageprocnear + mov ax,VGA_SEGMENT + mov es,ax + call GetImageOffset ;ES:DI is the destination address for the + ; image in VGA memory + mov dx,SC_INDEX + mov al,1 ;do plane 0 first +DrawImagePlaneLoop: + push di ;image is drawn at the same offset in + ; each plane + push ax ;preserve plane select + mov al,MAP_MASK ;Map Mask index + out dx,al ;point SC Index to the Map Mask register + pop ax ;get back plane select + inc dx ;point to SC index register + out dx,al ;set up the Map Mask to allow writes to + ; the plane of interest + dec dx ;point back to SC Data register + mov bx,IMAGE_HEIGHT ;# of scan lines in image +DrawImageLoop: + mov cx,IMAGE_WIDTH ;# of bytes across image + rep movsb + add di,SCREEN_WIDTH-IMAGE_WIDTH + ;point to next scan line of image + dec bx ;any more scan lines? + jnz DrawImageLoop + pop di ;get back image start offset in VGA memory + shl al,1 ;Map Mask setting for next plane + cmp al,10h ;have we done all four planes? + jnz DrawImagePlaneLoop + ret +DrawImageendp +; +; Copies the image from its current location in VGA memory into the +; buffer at DS:DI. +; +GetImage proc near + mov si,di ;move destination offset into SI + call GetImageOffset ;DI is offset of image in VGA memory + xchg si,di ;SI is offset of image, DI is destination offset + push ds + pop es ;ES:DI is destination + mov ax,VGA_SEGMENT + mov ds,ax ;DS:SI is source +; + mov dx,GC_INDEX + sub al,al;do plane 0 first +GetImagePlaneLoop: + push si ;image comes from same offset in each plane + push ax ;preserve plane select + mov al,READ_MAP ;Read Map index + out dx,al ;point GC Index to Read Map register + pop ax ;get back plane select + inc dx ;point to GC Index register + out dx,al ;set up the Read Map to select reads from + ; the plane of interest + dec dx ;point back to GC data register + mov bx,IMAGE_HEIGHT ;# of scan lines in image +GetImageLoop: + mov cx,IMAGE_WIDTH ;# of bytes across image + rep movsb + add si,SCREEN_WIDTH-IMAGE_WIDTH + ;point to next scan line of image + dec bx ;any more scan lines? + jnz GetImageLoop + pop si ;get back image start offset + inc al ;Read Map setting for next plane + cmp al,4 ;have we done all four planes? + jnz GetImagePlaneLoop + push es + pop ds ;restore original DS + ret +GetImageendp +; +; Erases the image at its current location. +; +EraseImage proc near + mov dx,SC_INDEX + mov al,MAP_MASK + out dx,al ;point SC Index to the Map Mask register + inc dx ;point to SC Data register + mov al,0fh + out dx,al ;set up the Map Mask to allow writes to go to + ; all 4 planes + mov ax,VGA_SEGMENT + mov es,ax + call GetImageOffset ;ES:DI points to the start address + ; of the image + sub al,al ;erase with zeros + mov bx,IMAGE_HEIGHT ;# of scan lines in image +EraseImageLoop: + mov cx,IMAGE_WIDTH ;# of bytes across image + rep stosb + add di,SCREEN_WIDTH-IMAGE_WIDTH + ;point to next scan line of image + dec bx ;any more scan lines? + jnz EraseImageLoop + ret +EraseImage endp +; +; Returns the current offset of the image in the VGA segment in DI. +; +GetImageOffset proc near + mov ax,SCREEN_WIDTH + mul [ImageY] + add ax,[ImageX] + mov di,ax + ret +GetImageOffset endp +code ends + end Start +``` diff --git a/28-04.md b/28-04.md index 1ffe214..fe7e186 100644 --- a/28-04.md +++ b/28-04.md @@ -12,167 +12,169 @@ pages: 531-535 **LISTING 28.2 L28-2.ASM** - ; Program to illustrate use of read mode 1 (color compare mode) - ; to detect collisions in display memory. Draws a yellow line on a - ; blue background, then draws a perpendicular green line until the - ; yellow line is reached. - ; - ; By Michael Abrash - ; - stack segment word stack `STACK' - db 512 dup (?) - stack ends - ; - VGA_SEGMENT EQU 0a000h - SCREEN_WIDTH EQU 80 ;in bytes - GC_INDEX EQU 3ceh ;Graphics Controller Index register - SET_RESET EQU 0 ;Set/Reset register index in GC - ENABLE_SET_RESET EQU 1 ;Enable Set/Reset register index in GC - COLOR_COMPARE EQU 2 ;Color Compare register index in GC - GRAPHICS_MODE EQU 5 ;Graphics Mode register index in GC - BIT_MASK EQU 8 ;Bit Mask register index in GC - ; - code segment word `CODE' - assume cs:code - Start proc near - cld - ; - ; Select graphics mode 10h. - ; - mov ax,10h - int 10h - ; - ; Fill the screen with blue. - ; - mov al,1 ;blue is color 1 - call SelectSetResetColor ;set to draw in blue - mov ax,VGA_SEGMENT - move s,ax - sub di,di - mov cx,7000h - rep stosb ;the value written actually doesn't - ; matter, since set/reset is providing - ; the data written to display memory - ; - ; Draw a vertical yellow line. - ; - mov al,14 ;yellow is color 14 - call SelectSetResetColor ;set to draw in yellow - mov dx,GC_INDEX - mov al,BIT_MASK - out dx,al ;point GC Index to Bit Mask - inc dx ;point to GC Data - mov al,10h - out dx,al ;set Bit Mask to 10h - mov di,40 ;start in the middle of the top line - mov cx,350 ;do full height of screen - VLineLoop: - mov al,es:[di] ;load the latches - stosb ;write next pixel of yellow line (set/reset - ; provides the data written to display - ; memory, and AL is actually ignored) - add di,SCREEN_WIDTH-1 ;point to the next scan line - loopVLineLoop - ; - ; Select write mode 0 and read mode 1. - ; - mov dx,GC_INDEX - mov al,GRAPHICS_MODE - out dx,al ;point GC Index to Graphics Mode register - inc dx ;point to GC Data - mov al,00001000b ;bit 3=1 is read mode 1, bits 1 & 0=00 - ; is write mode 0 - out dx,al ;set Graphics Mode to read mode 1, - ; write mode 0 - ; - ; Draw a horizontal green line, one pixel at a time, from left - ; to right until color compare reports a yellow pixel is encountered. - ; - ; Draw in green. - ; - mov al,2 ;green is color 2 - call SelectSetResetColor ;set to draw in green - ; - ; Set color compare to look for yellow. - ; - mov dx,GC_INDEX - mov al,COLOR_COMPARE - out dx,al ;point GC Index to Color Compare register - inc dx ;point to GC Data - mov al,14 ;we're looking for yellow, color 14 - out dx,al ;set color compare to look for yellow - dec dx ;point to GC Index - ; - ; Set up for quick access to Bit Mask register. - ; - mov al,BIT_MASK - out dx,al ;point GC Index to Bit Mask register - inc dx ;point to GC Data - ; - ; Set initial pixel mask and display memory offset. - ; - mov al,80h ;initial pixel mask - mov di,100*SCREEN_WIDTH - ;start at left edge of scan line 100 - HLineLoop: - mov ah,es:[di] ;do a read mode 1 (color compare) read. - ; This also loads the latches. - and ah,al ;is the pixel of current interest yellow? - jnz WaitKeyAndDone ;yes-we've reached the yellow line, so we're - ; done - out dx,al ;set the Bit Mask register so that we - ; modify only the pixel of interest - mov es:[di],al ;draw the pixel. The value written is - ; irrelevant, since set/reset is providing - ; the data written to display memory - ror al,1 ;shift pixel mask to the next pixel - adc di,0 ;advance the display memory offset if - ; the pixel mask wrapped - ; - ; Slow things down a bit for visibility (adjust as needed). - ; - mov cx,0 - DelayLoop: - loop DelayLoop - jmp HLineLoop - ; - ; Wait for a key to be pressed to end, then return to text mode and - ; return to DOS. - ; - WaitKeyAndDone: - WaitKeyLoop: - mov ah,1 - int 16h - jz WaitKeyLoop - sub ah,ah - int 16h ;clear the key - mov ax,3 - int 10h ;return to text mode - mov ah,4ch - int 21h ;done - Startendp - ; - ; Enables set/reset for all planes, and sets the set/reset color - ; to AL. - ; - SelectSetResetColorprocnear - mov dx,GC_INDEX - push ax ;preserve color - mov al,SET_RESET - out dx,al ;point GC Index to Set/Reset register - inc dx ;point to GC Data - pop ax ;get back color - out dx,al ;set Set/Reset register to selected color - dec dx ;point to GC Index - mov al,ENABLE_SET_RESET - out dx,al ;point GC Index to Enable Set/Reset register - inc dx ;point to GC Data - mov al,0fh - out dx,al ;enable set/reset for all planes - ret - SelectSetResetColorendp - code ends - end Start +```nasm +; Program to illustrate use of read mode 1 (color compare mode) +; to detect collisions in display memory. Draws a yellow line on a +; blue background, then draws a perpendicular green line until the +; yellow line is reached. +; +; By Michael Abrash +; +stack segment word stack `STACK' + db 512 dup (?) +stack ends +; +VGA_SEGMENT EQU 0a000h +SCREEN_WIDTH EQU 80 ;in bytes +GC_INDEX EQU 3ceh ;Graphics Controller Index register +SET_RESET EQU 0 ;Set/Reset register index in GC +ENABLE_SET_RESET EQU 1 ;Enable Set/Reset register index in GC +COLOR_COMPARE EQU 2 ;Color Compare register index in GC +GRAPHICS_MODE EQU 5 ;Graphics Mode register index in GC +BIT_MASK EQU 8 ;Bit Mask register index in GC +; +code segment word `CODE' + assume cs:code +Start proc near + cld +; +; Select graphics mode 10h. +; + mov ax,10h + int 10h +; +; Fill the screen with blue. +; + mov al,1 ;blue is color 1 + call SelectSetResetColor ;set to draw in blue + mov ax,VGA_SEGMENT + move s,ax + sub di,di + mov cx,7000h + rep stosb ;the value written actually doesn't +; matter, since set/reset is providing +; the data written to display memory +; +; Draw a vertical yellow line. +; + mov al,14 ;yellow is color 14 + call SelectSetResetColor ;set to draw in yellow + mov dx,GC_INDEX + mov al,BIT_MASK + out dx,al ;point GC Index to Bit Mask + inc dx ;point to GC Data + mov al,10h + out dx,al ;set Bit Mask to 10h + mov di,40 ;start in the middle of the top line + mov cx,350 ;do full height of screen +VLineLoop: + mov al,es:[di] ;load the latches + stosb ;write next pixel of yellow line (set/reset +; provides the data written to display +; memory, and AL is actually ignored) + add di,SCREEN_WIDTH-1 ;point to the next scan line +loopVLineLoop +; +; Select write mode 0 and read mode 1. +; + mov dx,GC_INDEX + mov al,GRAPHICS_MODE + out dx,al ;point GC Index to Graphics Mode register + inc dx ;point to GC Data + mov al,00001000b ;bit 3=1 is read mode 1, bits 1 & 0=00 + ; is write mode 0 + out dx,al ;set Graphics Mode to read mode 1, + ; write mode 0 +; +; Draw a horizontal green line, one pixel at a time, from left +; to right until color compare reports a yellow pixel is encountered. +; +; Draw in green. +; + mov al,2 ;green is color 2 + call SelectSetResetColor ;set to draw in green +; +; Set color compare to look for yellow. +; + mov dx,GC_INDEX + mov al,COLOR_COMPARE + out dx,al ;point GC Index to Color Compare register + inc dx ;point to GC Data + mov al,14 ;we're looking for yellow, color 14 + out dx,al ;set color compare to look for yellow + dec dx ;point to GC Index +; +; Set up for quick access to Bit Mask register. +; + mov al,BIT_MASK + out dx,al ;point GC Index to Bit Mask register + inc dx ;point to GC Data +; +; Set initial pixel mask and display memory offset. +; + mov al,80h ;initial pixel mask + mov di,100*SCREEN_WIDTH + ;start at left edge of scan line 100 +HLineLoop: + mov ah,es:[di] ;do a read mode 1 (color compare) read. + ; This also loads the latches. + and ah,al ;is the pixel of current interest yellow? + jnz WaitKeyAndDone ;yes-we've reached the yellow line, so we're + ; done + out dx,al ;set the Bit Mask register so that we + ; modify only the pixel of interest + mov es:[di],al ;draw the pixel. The value written is + ; irrelevant, since set/reset is providing + ; the data written to display memory + ror al,1 ;shift pixel mask to the next pixel + adc di,0 ;advance the display memory offset if + ; the pixel mask wrapped +; +; Slow things down a bit for visibility (adjust as needed). +; + mov cx,0 +DelayLoop: + loop DelayLoop + jmp HLineLoop +; +; Wait for a key to be pressed to end, then return to text mode and +; return to DOS. +; +WaitKeyAndDone: +WaitKeyLoop: + mov ah,1 + int 16h + jz WaitKeyLoop + sub ah,ah + int 16h ;clear the key + mov ax,3 + int 10h ;return to text mode + mov ah,4ch + int 21h ;done +Startendp +; +; Enables set/reset for all planes, and sets the set/reset color +; to AL. +; +SelectSetResetColorprocnear + mov dx,GC_INDEX + push ax ;preserve color + mov al,SET_RESET + out dx,al ;point GC Index to Set/Reset register + inc dx ;point to GC Data + pop ax ;get back color + out dx,al ;set Set/Reset register to selected color + dec dx ;point to GC Index + mov al,ENABLE_SET_RESET + out dx,al ;point GC Index to Enable Set/Reset register + inc dx ;point to GC Data + mov al,0fh + out dx,al ;enable set/reset for all planes + ret +SelectSetResetColorendp +code ends +end Start +``` ### When all Planes "Don't Care" {#Heading5} diff --git a/28-05.md b/28-05.md index 5226b82..07b3787 100644 --- a/28-05.md +++ b/28-05.md @@ -30,109 +30,111 @@ excellent pixel- and line-drawing code. **LISTING 28.3 L28-3.ASM** - ; Program that draws a diagonal line to illustrate the use of a - ; Color Don't Care register setting of 0FFH to support fast - ; read-modify-write operations to VGA memory in write mode 3 by - ; drawing a diagonal line. - ; - ; Note: Works on VGAs only. - ; - ; By Michael Abrash - ; - stack segment word stack 'STACK' - db 512 dup (?) - stackends - ; - VGA_SEGMENT EQU 0a000h - SCREEN_WIDTH EQU 80 ;in bytes - GC_INDEX EQU 3ceh ;Graphics Controller Index register - SET_RESET EQU 0 ;Set/Reset register index in GC - ENABLE_SET_RESET EQU 1 ;Enable Set/Reset register index in GC - GRAPHICS_MODE EQU 5 ;Graphics Mode register index in GC - COLOR_DONT_CARE EQU 7 ;Color Don't Care register index in GC - ; - code segment word 'CODE' - assume cs:code - Startprocnear - ; - ; Select graphics mode 12h. - ; - mov ax,12h - int 10h - ; - ; Select write mode 3 and read mode 1. - ; - mov dx,GC_INDEX - mov al,GRAPHICS_MODE - out dx,al - inc dx - in al,dx ;VGA registers are readable, bless them! - or al,00001011b ;bit 3=1 selects read mode 1, and - ; bits 1 & 0=11 selects write mode 3 - jmp $+2 ;delay between IN and OUT to same port - out dx,al - dec dx - ; - ; Set up set/reset to always draw in white. - ; - mov al,SET_RESET - out dx,al - inc dx - mov al,0fh - out dx,al - dec dx - mov al,ENABLE_SET_RESET - out dx,al - inc dx - mov al,0fh - out dx,al - dec dx - ; - ; Set Color Don't Care to 0, so reads of VGA memory always return 0FFH. - ; - mov al,COLOR_DONT_CARE - out dx,al - inc dx - sub al,al - out dx,al - ; - ; Set up the initial memory pointer and pixel mask. - ; - mov ax,VGA_SEGMENT - mov ds,ax - sub bx,bx - mov al,80h - ; - ; Draw 400 points on a diagonal line sloping down and to the right. - ; - mov cx,400 - DrawDiagonalLoop: - and [bx],al ;reads display memory, loading the latches, - ; then writes AL to the VGA. AL becomes the - ; bit mask, and set/reset provides the - ; actual data written - add bx,SCREEN_WIDTH - ; point to the next scan line - ror al,1 ;move the pixel mask one pixel to the right - adc bx,0 ;advance to the next byte if the pixel mask wrapped - loopDrawDiagonalLoop - ; - ; Wait for a key to be pressed to end, then return to text mode and - ; return to DOS. - ; - WaitKeyLoop: - mov ah,1 - int 16h - jz WaitKeyLoop - sub ah,ah - int 16h ;clear the key - mov ax,3 - int 10h ;return to text mode - mov ah,4ch - int 21h ;done - Startendp - code ends - end Start +```nasm +; Program that draws a diagonal line to illustrate the use of a +; Color Don't Care register setting of 0FFH to support fast +; read-modify-write operations to VGA memory in write mode 3 by +; drawing a diagonal line. +; +; Note: Works on VGAs only. +; +; By Michael Abrash +; +stack segment word stack 'STACK' + db 512 dup (?) +stackends +; +VGA_SEGMENT EQU 0a000h +SCREEN_WIDTH EQU 80 ;in bytes +GC_INDEX EQU 3ceh ;Graphics Controller Index register +SET_RESET EQU 0 ;Set/Reset register index in GC +ENABLE_SET_RESET EQU 1 ;Enable Set/Reset register index in GC +GRAPHICS_MODE EQU 5 ;Graphics Mode register index in GC +COLOR_DONT_CARE EQU 7 ;Color Don't Care register index in GC +; +code segment word 'CODE' + assume cs:code +Startprocnear +; +; Select graphics mode 12h. +; + mov ax,12h + int 10h +; +; Select write mode 3 and read mode 1. +; + mov dx,GC_INDEX + mov al,GRAPHICS_MODE + out dx,al + inc dx + in al,dx ;VGA registers are readable, bless them! + or al,00001011b ;bit 3=1 selects read mode 1, and + ; bits 1 & 0=11 selects write mode 3 + jmp $+2 ;delay between IN and OUT to same port + out dx,al + dec dx +; +; Set up set/reset to always draw in white. +; + mov al,SET_RESET + out dx,al + inc dx + mov al,0fh + out dx,al + dec dx + mov al,ENABLE_SET_RESET + out dx,al + inc dx + mov al,0fh + out dx,al + dec dx +; +; Set Color Don't Care to 0, so reads of VGA memory always return 0FFH. +; + mov al,COLOR_DONT_CARE + out dx,al + inc dx + sub al,al + out dx,al +; +; Set up the initial memory pointer and pixel mask. +; + mov ax,VGA_SEGMENT + mov ds,ax + sub bx,bx + mov al,80h +; +; Draw 400 points on a diagonal line sloping down and to the right. +; + mov cx,400 +DrawDiagonalLoop: + and [bx],al ;reads display memory, loading the latches, + ; then writes AL to the VGA. AL becomes the + ; bit mask, and set/reset provides the + ; actual data written + add bx,SCREEN_WIDTH + ; point to the next scan line + ror al,1 ;move the pixel mask one pixel to the right + adc bx,0 ;advance to the next byte if the pixel mask wrapped +loopDrawDiagonalLoop +; +; Wait for a key to be pressed to end, then return to text mode and +; return to DOS. +; +WaitKeyLoop: + mov ah,1 + int 16h + jz WaitKeyLoop + sub ah,ah + int 16h ;clear the key + mov ax,3 + int 10h ;return to text mode + mov ah,4ch + int 21h ;done +Startendp +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 diff --git a/29-01.md b/29-01.md index c3491ee..2c08099 100644 --- a/29-01.md +++ b/29-01.md @@ -70,131 +70,133 @@ by Listing 29.1 back into the mode 10H frame buffer. **LISTING 29.1 L29-1.ASM** - ; Program to put up a mode 10h EGA graphics screen, then save it - ; to the file SNAPSHOT.SCR. - ; - VGA_SEGMENT equ 0a000h - GC_INDEX equ 3ceh ;Graphics Controller Index register - READ_MAP equ 4 ;Read Map register index in GC - DISPLAYED_SCREEN_SIZE equ (640/8)*350 ;# of displayed bytes per plane in a - ; hi-res graphics screen - ; - 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 - Plane db ? ;plane being read - Data ends - ; - Code segment - assume cs:Code, ds:Data - Start proc near - mov ax,Data - mov ds,ax - ; - ; Go to hi-res graphics mode. - ; - mov ax,10h ;AH = 0 means mode set, AL = 10h selects - ; hi-res graphics mode - int 10h ;BIOS video interrupt - ; - ; Put up some text, so the screen isn't empty. - ; - mov ah,9 ;DOS print string function - mov dx,offset SampleText - int 21h - ; - ; Delete SNAPSHOT.SCR if it exists. - ; - mov ah,41h ;DOS unlink file function - mov dx,offset Filename - int 21h - ; - ; Create the file SNAPSHOT.SCR. - ; - mov ah,3ch ;DOS create file function - mov dx,offset Filename - 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 - mov ah,9 ;DOS print string function - mov dx,offset ErrMsg1 - int 21h ;notify of the error - jmp short Done ;and done - ; - ; Loop through the 4 planes, making each readable in turn and - ; writing it to disk. Note that all 4 planes are readable at - ; A000:0000; the Read Map register selects which plane is readable - ; at any one time. - ; - SaveTheScreen: - mov [Plane],0;start with plane 0 - SaveLoop: - mov dx,GC_INDEX - mov al,READ_MAP;set GC Index to Read Map register - out dx,al - inc dx - mov al,[Plane] ;get the # of the plane we want - ; to save - out dx,al ;set to read from the desired plane - mov ah,40h ;DOS write to file function - mov bx,[Handle] - mov cx,DISPLAYED_SCREEN_SIZE ;# of bytes to save - sub dx,dx ;write all displayed bytes at A000:0000 - push ds - mov si,VGA_SEGMENT - mov ds,si - int 21h ;write the displayed portion of this plane - pop ds - cmp ax,DISPLAYED_SCREEN_SIZE ;did all bytes get written? - jz SaveLoopBottom - mov ah,9 ;DOS print string function - mov dx,offset ErrMsg2 - int 21h ;notify about the error - jmp short DoClose ;and done - SaveLoopBottom: - mov al,[Plane] - inc ax ;point to the next plane - mov [Plane],al - cmp al,3 ;have we done all planes? - jbe SaveLoop ;no, so do the next plane - ; - ; Close SNAPSHOT.SCR. - ; - DoClose: - mov ah,3eh ;DOS close file function - mov bx,[Handle] - int 21h - ; - ; Wait for a keypress. - ; - mov ah,9 ;DOS print string function - mov dx,offset WaitKeyMsg - int 21h ;prompt - mov ah,8 ;DOS input without echo function - int 21h - ; - ; Restore text mode. - ; - mov ax,3 - int 10h - ; - ; Done. - ; - Done: - mov ah,4ch;DOS terminate function - int 21h - Start endp - Code ends - end Start +```nasm +; Program to put up a mode 10h EGA graphics screen, then save it +; to the file SNAPSHOT.SCR. +; +VGA_SEGMENT equ 0a000h +GC_INDEX equ 3ceh ;Graphics Controller Index register +READ_MAP equ 4 ;Read Map register index in GC +DISPLAYED_SCREEN_SIZE equ (640/8)*350 ;# of displayed bytes per plane in a + ; hi-res graphics screen +; +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 +Plane db ? ;plane being read +Data ends +; +Code segment + assume cs:Code, ds:Data +Start proc near + mov ax,Data + mov ds,ax +; +; Go to hi-res graphics mode. +; + mov ax,10h ;AH = 0 means mode set, AL = 10h selects + ; hi-res graphics mode + int 10h ;BIOS video interrupt +; +; Put up some text, so the screen isn't empty. +; + mov ah,9 ;DOS print string function + mov dx,offset SampleText + int 21h +; +; Delete SNAPSHOT.SCR if it exists. +; + mov ah,41h ;DOS unlink file function + mov dx,offset Filename + int 21h +; +; Create the file SNAPSHOT.SCR. +; + mov ah,3ch ;DOS create file function + mov dx,offset Filename + 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 + mov ah,9 ;DOS print string function + mov dx,offset ErrMsg1 + int 21h ;notify of the error + jmp short Done ;and done +; +; Loop through the 4 planes, making each readable in turn and +; writing it to disk. Note that all 4 planes are readable at +; A000:0000; the Read Map register selects which plane is readable +; at any one time. +; +SaveTheScreen: + mov [Plane],0;start with plane 0 +SaveLoop: + mov dx,GC_INDEX + mov al,READ_MAP;set GC Index to Read Map register + out dx,al + inc dx + mov al,[Plane] ;get the # of the plane we want + ; to save + out dx,al ;set to read from the desired plane + mov ah,40h ;DOS write to file function + mov bx,[Handle] + mov cx,DISPLAYED_SCREEN_SIZE ;# of bytes to save + sub dx,dx ;write all displayed bytes at A000:0000 + push ds + mov si,VGA_SEGMENT + mov ds,si + int 21h ;write the displayed portion of this plane + pop ds + cmp ax,DISPLAYED_SCREEN_SIZE ;did all bytes get written? + jz SaveLoopBottom + mov ah,9 ;DOS print string function + mov dx,offset ErrMsg2 + int 21h ;notify about the error + jmp short DoClose ;and done +SaveLoopBottom: + mov al,[Plane] + inc ax ;point to the next plane + mov [Plane],al + cmp al,3 ;have we done all planes? + jbe SaveLoop ;no, so do the next plane +; +; Close SNAPSHOT.SCR. +; +DoClose: + mov ah,3eh ;DOS close file function + mov bx,[Handle] + int 21h +; +; Wait for a keypress. +; + mov ah,9 ;DOS print string function + mov dx,offset WaitKeyMsg + int 21h ;prompt + mov ah,8 ;DOS input without echo function + int 21h +; +; Restore text mode. +; + mov ax,3 + int 10h +; +; Done. +; +Done: + mov ah,4ch;DOS terminate function + int 21h +Start endp +Code ends + end Start +``` diff --git a/29-02.md b/29-02.md index 266ed0e..a614d16 100644 --- a/29-02.md +++ b/29-02.md @@ -12,120 +12,122 @@ pages: 545-547 **LISTING 29.2 L29-2.ASM** - ; Program to restore a mode 10h EGA graphics screen from - ; the file SNAPSHOT.SCR. - ; - VGA_SEGMENT equ 0a000h - SC_INDEX equ 3c4h ;Sequence Controller Index register - MAP_MASK equ 2 ;Map Mask register index in SC - DISPLAYED_SCREEN_SIZE equ (640/8)*350 ;# of displayed bytes per plane in a - ; hi-res graphics screen - ; - 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 - Plane db ? ;plane being written - Data ends - ; - Code segment - assume cs:Code, ds:Data - Start proc near - mov ax,Data - mov ds,ax - ; - ; Go to hi-res graphics mode. - ; - mov ax,10h ;AH = 0 means mode set, AL = 10h selects - ; hi-res graphics mode - int 10h ;BIOS video interrupt - ; - ; Open SNAPSHOT.SCR. - ; - mov ah,3dh ;DOS open file function - mov dx,offset Filename - sub al,al ;open for reading - int 21h - mov [Handle],ax ;save the handle - 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 - jmp short Done;and done - ; - ; Loop through the 4 planes, making each writable in turn and - ; reading it from disk. Note that all 4 planes are writable at - ; A000:0000; the Map Mask register selects which planes are readable - ; at any one time. We only make one plane readable at a time. - ; - RestoreTheScreen: - mov [Plane],0 ;start with plane 0 - RestoreLoop: - mov dx,SC_INDEX - mov al,MAP_MASK ;set SC Index to Map Mask register - outdx,al - inc dx - mov cl,[Plane] ;get the # of the plane we want - ; to restore - mov al,1 - shl al,cl ;set the bit enabling writes to - ; only the one desired plane - out dx,al ;set to read from desired plane - mov ah,3fh ;DOS read from file function - mov bx,[Handle] - mov cx,DISPLAYED_SCREEN_SIZE ;# of bytes to read - sub dx,dx ;start loading bytes at A000:0000 - push ds - mov si,VGA_SEGMENT - mov ds,si - int 21h ;read the displayed portion of this plane - pop ds - jc ReadError - cmp ax,DISPLAYED_SCREEN_SIZE ;did all bytes get read? - jz RestoreLoopBottom - ReadError: - mov ah,9 ;DOS print string function - mov dx,offset ErrMsg2 - int 21h ;notify about the error - jmp short DoClose ;and done - RestoreLoopBottom: - mov al,[Plane] - inc ax ;point to the next plane - mov [Plane],al - cmp al,3 ;have we done all planes? - jbe RestoreLoop ;no, so do the next plane - ; - ; Close SNAPSHOT.SCR. - ; - DoClose: - mov ah,3eh ;DOS close file function - mov bx,[Handle] - int 21h - ; - ; Wait for a keypress. - ; - mov ah,8 ;DOS input without echo function - int 21h - ; - ; Restore text mode. - ; - mov ax,3 - int 10h - ; - ; Done. - ; - Done: - mov ah,4ch ;DOS terminate function - int 21h - Start endp - Code ends - end Start +```nasm +; Program to restore a mode 10h EGA graphics screen from +; the file SNAPSHOT.SCR. +; +VGA_SEGMENT equ 0a000h +SC_INDEX equ 3c4h ;Sequence Controller Index register +MAP_MASK equ 2 ;Map Mask register index in SC +DISPLAYED_SCREEN_SIZE equ (640/8)*350 ;# of displayed bytes per plane in a + ; hi-res graphics screen +; +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 +Plane db ? ;plane being written +Data ends +; +Code segment + assume cs:Code, ds:Data +Start proc near + mov ax,Data + mov ds,ax +; +; Go to hi-res graphics mode. +; + mov ax,10h ;AH = 0 means mode set, AL = 10h selects + ; hi-res graphics mode + int 10h ;BIOS video interrupt +; +; Open SNAPSHOT.SCR. +; + mov ah,3dh ;DOS open file function + mov dx,offset Filename + sub al,al ;open for reading + int 21h + mov [Handle],ax ;save the handle + 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 + jmp short Done;and done +; +; Loop through the 4 planes, making each writable in turn and +; reading it from disk. Note that all 4 planes are writable at +; A000:0000; the Map Mask register selects which planes are readable +; at any one time. We only make one plane readable at a time. +; +RestoreTheScreen: + mov [Plane],0 ;start with plane 0 +RestoreLoop: + mov dx,SC_INDEX + mov al,MAP_MASK ;set SC Index to Map Mask register +outdx,al + inc dx + mov cl,[Plane] ;get the # of the plane we want +; to restore + mov al,1 + shl al,cl ;set the bit enabling writes to + ; only the one desired plane + out dx,al ;set to read from desired plane + mov ah,3fh ;DOS read from file function + mov bx,[Handle] + mov cx,DISPLAYED_SCREEN_SIZE ;# of bytes to read + sub dx,dx ;start loading bytes at A000:0000 + push ds + mov si,VGA_SEGMENT + mov ds,si + int 21h ;read the displayed portion of this plane + pop ds + jc ReadError + cmp ax,DISPLAYED_SCREEN_SIZE ;did all bytes get read? + jz RestoreLoopBottom +ReadError: + mov ah,9 ;DOS print string function + mov dx,offset ErrMsg2 + int 21h ;notify about the error + jmp short DoClose ;and done +RestoreLoopBottom: + mov al,[Plane] + inc ax ;point to the next plane + mov [Plane],al + cmp al,3 ;have we done all planes? + jbe RestoreLoop ;no, so do the next plane +; +; Close SNAPSHOT.SCR. +; +DoClose: + mov ah,3eh ;DOS close file function + mov bx,[Handle] + int 21h +; +; Wait for a keypress. +; + mov ah,8 ;DOS input without echo function + int 21h +; +; Restore text mode. +; + mov ax,3 + int 10h +; +; Done. +; +Done: + mov ah,4ch ;DOS terminate function + int 21h +Start endp +Code ends + end Start +``` If you compare Listings 29.1 and 29.2, you will see that the Map Mask register setting used to load a given plane does not match the Read Map diff --git a/29-04.md b/29-04.md index c3bb7e5..56be7dd 100644 --- a/29-04.md +++ b/29-04.md @@ -67,253 +67,255 @@ 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. - ; - VGA_SEGMENT equ 0a000h - SC_INDEX equ 3c4h ;Sequence Controller Index register - MAP_MASK equ 2 ;Map Mask register index in SC - BAR_HEIGHT equ 14 ;height of each bar - TOP_BAR equ BAR_HEIGHT*6 ;start the bars down a bit to - ; leave room for text - ; - 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.' - db 0dh, 0ah, 0ah, 0ah, 0ah - db 13 dup (‘ '), ‘Attribute' - db 38 dup (‘ '), ‘Color$' - ; - ; Used to label the attributes of the color bars. - ; - AttributeNumbers label byte - x= 0 - rept 16 - if x lt 10 - db ‘0', x+‘0', ‘h', 0ah, 8, 8, 8 - else - db ‘0', x+‘A'-10, ‘h', 0ah, 8, 8, 8 - endif - x= x+1 - endm - 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 - endm - COLOR_ENTRY_LENGTHequ($-ColorNumbers)/16 - db ‘$' - ; - CurrentColordb? - ; - ; Space for the array of 16 colors we'll pass to the BIOS, plus - ; an overscan setting of black. - ; - ColorTable db 16 dup (?), 0 - Data ends - ; - Code segment - assume cs:Code, ds:Data - Start procnear - cld - mov ax,Data - mov ds,ax - ; - ; Go to hi-res graphics mode. - ; - mov ax,10h ;AH = 0 means mode set, AL = 10h selects - ; hi-res graphics mode - int 10h ;BIOS video interrupt - ; - ; Put up relevant text. - ; - mov ah,9 ;DOS print string function - mov dx,offset KeyMsg - int 21h - ; - ; Put up the color bars, one in each of the 16 possible pixel values - ; (which we'll call attributes). - ; - mov cx,16 ;we'll put up 16 color bars - sub al,al ;start with attribute 0 - BarLoop: - push ax - push cx - call BarUp - pop cx - pop ax - inc ax ;select the next attribute - loop BarLoop - ; - ; Put up the attribute labels. - ; - mov ah,2 ;video interrupt set cursor position function - sub bh,bh ;page 0 - mov dh,TOP_BAR/14 ;counting in character rows, match to - ; top of first bar, counting in - ; scan lines - mov dl,16 ;just to left of bars - int 10h - mov ah,9 ;DOS print string function - mov dx,offset AttributeNumbers - int 21h - ; - ; Loop through the color set, one new setting per keypress. - ; - mov [CurrentColor],0 ;start with color zero - ColorLoop: - ; - ; Set the palette registers to the current color set, consisting - ; of the current color mapped to attribute 0, current color + 1 - ; mapped to attribute 1, and so on. - ; - mov al,[CurrentColor] - mov bx,offset ColorTable - mov cx,16 ;we have 16 colors to set - PaletteSetLoop: - and al,3fh ;limit to 6-bit color values - mov [bx],al ;build the 16-color table used for setting - inc bx ; the palette registers - inc ax - loop PaletteSetLoop - mov ah,10h ;video interrupt palette function - mov al,2 ;subfunction to set all 16 palette registers - ; and overscan at once - mov dx,offset ColorTable - push ds - pop es ;ES:DX points to the color table - int 10h ;invoke the video interrupt to set the palette - ; - ; Put up the color numbers, so we can see how attributes map - ; to color values, and so we can see how each color # looks - ; (at least on this particular screen). - ; - call ColorNumbersUp - ; - ; Wait for a keypress, so they can see this color set. - ; - WaitKey: - mov ah,8 ;DOS input without echo function - int 21h - ; - ; Advance to the next color set. - ; - mov al,[CurrentColor] - inc ax - mov [CurrentColor],al - cmp al,64 - jbe ColorLoop - ; - ; Restore text mode. - ; - mov ax,3 - int 10h - ; - ; Done. - ; - Done: - mov ah,4ch ;DOS terminate function - int 21h - ; - ; Puts up a bar consisting of the specified attribute (pixel value), - ; at a vertical position corresponding to the attribute. - ; - ; Input: AL = attribute - ; - BarUp proc near - mov dx,SC_INDEX - mov ah,al - mov al,MAP_MASK - out dx,al - inc dx - mov al,ah - out dx,al ;set the Map Mask register to produce - ; the desired color - mov ah,BAR_HEIGHT - mul ah ;row of top of bar - add ax,TOP_BAR ;start a few lines down to leave room for - ; text - mov dx,80 ;rows are 80 bytes long - mul dx ;offset in bytes of start of scan line bar - ; starts on - add ax,20 ;offset in bytes of upper left corner of bar - mov di,ax - mov ax,VGA_SEGMENT - mov es,ax ;ES:DI points to offset of upper left - ; corner of bar - mov dx,BAR_HEIGHT - mov al,0ffh - BarLineLoop: - mov cx,40 ;make the bars 40 wide - rep stosb ;do one scan line of the bar - add di,40 ;point to the start of the next scan line - ; of the bar - dec dx - jnz BarLineLoop - ret - BarUp endp - ; - ; Converts AL to a hex digit in the range 0-F. - ; - BinToHexDigit proc near - cmp al,9 - ja IsHex - add al,‘0' - ret - IsHex: - add al,‘A'-10 - ret - BinToHexDigit endp - ; - ; Displays the color values generated by the color bars given the - ; current palette register settings off to the right of the color - ; bars. - ; - ColorNumbersUp proc near - mov ah,2 ;video interrupt set cursor position function - sub bh,bh ;page 0 - mov dh,TOP_BAR/14 ;counting in character rows, match to - ; top of first bar, counting in - ; scan lines - mov dl,20+40+1 ;just to right of bars - int 10h - 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 - ColorNumberLoop: - pus hax;save the color # - and al,3fh;limit to 6-bit color values - shr al,1 - shr al,1 - shr al,1 - shr al,1 ;isolate the high nibble of the color # - call BinToHexDigit ;convert the high color # nibble - mov [bx],al ; and put it into the text - pop ax ;get back the color # - push ax ;save the color # - and al,0fh ;isolate the low color # nibble - call BinToHexDigit ;convert the low nibble of the - ; color # to ASCII - mov [bx+1],al ; and put it into the text - add bx,COLOR_ENTRY_LENGTH ;point to the next entry - pop ax ;get back the color # - inc ax ;next color # - loop ColorNumberLoop - mov ah,9 ;DOS print string function - mov dx,offset ColorNumbers - int 21h ;put up the attribute numbers - ret - ColorNumbersUpendp - ; - Start endp - Code ends - end Start +```nasm +; Program to illustrate the color mapping capabilities of the +; EGA's palette registers. +; +VGA_SEGMENT equ 0a000h +SC_INDEX equ 3c4h ;Sequence Controller Index register +MAP_MASK equ 2 ;Map Mask register index in SC +BAR_HEIGHT equ 14 ;height of each bar +TOP_BAR equ BAR_HEIGHT*6 ;start the bars down a bit to + ; leave room for text +; +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.' + db 0dh, 0ah, 0ah, 0ah, 0ah + db 13 dup (‘ '), ‘Attribute' + db 38 dup (‘ '), ‘Color$' +; +; Used to label the attributes of the color bars. +; +AttributeNumbers label byte +x= 0 + rept 16 +if x lt 10 + db ‘0', x+‘0', ‘h', 0ah, 8, 8, 8 +else + db ‘0', x+‘A'-10, ‘h', 0ah, 8, 8, 8 +endif +x= x+1 + endm + 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 + endm +COLOR_ENTRY_LENGTHequ($-ColorNumbers)/16 + db ‘$' +; +CurrentColordb? +; +; Space for the array of 16 colors we'll pass to the BIOS, plus +; an overscan setting of black. +; +ColorTable db 16 dup (?), 0 +Data ends +; +Code segment + assume cs:Code, ds:Data +Start procnear + cld + mov ax,Data + mov ds,ax +; +; Go to hi-res graphics mode. +; + mov ax,10h ;AH = 0 means mode set, AL = 10h selects + ; hi-res graphics mode + int 10h ;BIOS video interrupt +; +; Put up relevant text. +; + mov ah,9 ;DOS print string function + mov dx,offset KeyMsg + int 21h +; +; Put up the color bars, one in each of the 16 possible pixel values +; (which we'll call attributes). +; + mov cx,16 ;we'll put up 16 color bars + sub al,al ;start with attribute 0 +BarLoop: + push ax + push cx + call BarUp + pop cx + pop ax + inc ax ;select the next attribute + loop BarLoop +; +; Put up the attribute labels. +; + mov ah,2 ;video interrupt set cursor position function + sub bh,bh ;page 0 + mov dh,TOP_BAR/14 ;counting in character rows, match to + ; top of first bar, counting in + ; scan lines + mov dl,16 ;just to left of bars + int 10h + mov ah,9 ;DOS print string function + mov dx,offset AttributeNumbers + int 21h +; +; Loop through the color set, one new setting per keypress. +; + mov [CurrentColor],0 ;start with color zero +ColorLoop: +; +; Set the palette registers to the current color set, consisting +; of the current color mapped to attribute 0, current color + 1 +; mapped to attribute 1, and so on. +; + mov al,[CurrentColor] + mov bx,offset ColorTable + mov cx,16 ;we have 16 colors to set +PaletteSetLoop: + and al,3fh ;limit to 6-bit color values + mov [bx],al ;build the 16-color table used for setting + inc bx ; the palette registers + inc ax + loop PaletteSetLoop + mov ah,10h ;video interrupt palette function + mov al,2 ;subfunction to set all 16 palette registers + ; and overscan at once + mov dx,offset ColorTable + push ds + pop es ;ES:DX points to the color table + int 10h ;invoke the video interrupt to set the palette +; +; Put up the color numbers, so we can see how attributes map +; to color values, and so we can see how each color # looks +; (at least on this particular screen). +; + call ColorNumbersUp +; +; Wait for a keypress, so they can see this color set. +; +WaitKey: + mov ah,8 ;DOS input without echo function + int 21h +; +; Advance to the next color set. +; + mov al,[CurrentColor] + inc ax + mov [CurrentColor],al + cmp al,64 + jbe ColorLoop +; +; Restore text mode. +; + mov ax,3 + int 10h +; +; Done. +; +Done: + mov ah,4ch ;DOS terminate function + int 21h +; +; Puts up a bar consisting of the specified attribute (pixel value), +; at a vertical position corresponding to the attribute. +; +; Input: AL = attribute +; +BarUp proc near + mov dx,SC_INDEX + mov ah,al + mov al,MAP_MASK + out dx,al + inc dx + mov al,ah + out dx,al ;set the Map Mask register to produce + ; the desired color + mov ah,BAR_HEIGHT + mul ah ;row of top of bar + add ax,TOP_BAR ;start a few lines down to leave room for + ; text + mov dx,80 ;rows are 80 bytes long + mul dx ;offset in bytes of start of scan line bar + ; starts on + add ax,20 ;offset in bytes of upper left corner of bar + mov di,ax + mov ax,VGA_SEGMENT + mov es,ax ;ES:DI points to offset of upper left + ; corner of bar + mov dx,BAR_HEIGHT + mov al,0ffh +BarLineLoop: + mov cx,40 ;make the bars 40 wide + rep stosb ;do one scan line of the bar + add di,40 ;point to the start of the next scan line + ; of the bar + dec dx + jnz BarLineLoop + ret +BarUp endp +; +; Converts AL to a hex digit in the range 0-F. +; +BinToHexDigit proc near + cmp al,9 + ja IsHex + add al,‘0' + ret +IsHex: + add al,‘A'-10 + ret +BinToHexDigit endp +; +; Displays the color values generated by the color bars given the +; current palette register settings off to the right of the color +; bars. +; +ColorNumbersUp proc near + mov ah,2 ;video interrupt set cursor position function + sub bh,bh ;page 0 + mov dh,TOP_BAR/14 ;counting in character rows, match to + ; top of first bar, counting in + ; scan lines + mov dl,20+40+1 ;just to right of bars + int 10h + 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 +ColorNumberLoop: + pus hax;save the color # + and al,3fh;limit to 6-bit color values + shr al,1 + shr al,1 + shr al,1 + shr al,1 ;isolate the high nibble of the color # + call BinToHexDigit ;convert the high color # nibble + mov [bx],al ; and put it into the text + pop ax ;get back the color # + push ax ;save the color # + and al,0fh ;isolate the low color # nibble + call BinToHexDigit ;convert the low nibble of the + ; color # to ASCII + mov [bx+1],al ; and put it into the text + add bx,COLOR_ENTRY_LENGTH ;point to the next entry + pop ax ;get back the color # + inc ax ;next color # + loop ColorNumberLoop + mov ah,9 ;DOS print string function + mov dx,offset ColorNumbers + int 21h ;put up the attribute numbers + ret +ColorNumbersUpendp +; +Start endp +Code ends + end Start +``` diff --git a/29-05.md b/29-05.md index fcbb94a..4a880f8 100644 --- a/29-05.md +++ b/29-05.md @@ -43,84 +43,86 @@ screen blanking. **LISTING 29.4 L29-4.ASM** - ; Program to demonstrate screen blanking via bit 5 of the - ; Attribute Controller Index register. - ; - AC_INDEX equ 3c0h ;Attribute Controller Index register - INPUT_STATUS_1 equ 3dah ;color-mode address of the Input - ; Status 1 register - ; - ; Macro to wait for and clear the next keypress. - ; - WAIT_KEY macro - mov ah,8 ;DOS input without echo function - int 21h - endm - ; - 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 ends - ; - Code segment - assume cs:Code, ds:Data - Start proc near - mov ax,Data - mov ds,ax - ; - ; Go to hi-res graphics mode. - ; - mov ax,10h ;AH = 0 means mode set, AL = 10h selects - ; hi-res graphics mode - int 10h ;BIOS video interrupt - ; - ; Put up some text, so the screen isn't empty. - ; - mov ah,9 ;DOS print string function - mov dx,offset SampleText - int 21h - ; - WAIT_KEY - ; - ; Blank the screen. - ; - mov dx,INPUT_STATUS_1 - in al,dx ;reset port 3c0h to index (rather than data) - ; mode - mov dx,AC_INDEX - sub al,al ;make bit 5 zero... - out dx,al ;...which blanks the screen - ; - WAIT_KEY - ; - ; Unblank the screen. - ; - mov dx,INPUT_STATUS_1 - in al,dx ;reset port 3c0h to Index (rather than data) - ; mode - mov dx,AC_INDEX - mov al,20h ;make bit 5 one... - out dx,al ;...which unblanks the screen - ; - WAIT_KEY - ; - ; Restore text mode. - ; - mov ax,2 - int 10h - ; - ; Done. - ; - Done: - mov ah,4ch ;DOS terminate function - int 21h - Start endp - Code ends - end Start +```nasm +; Program to demonstrate screen blanking via bit 5 of the +; Attribute Controller Index register. +; +AC_INDEX equ 3c0h ;Attribute Controller Index register +INPUT_STATUS_1 equ 3dah ;color-mode address of the Input + ; Status 1 register +; +; Macro to wait for and clear the next keypress. +; +WAIT_KEY macro + mov ah,8 ;DOS input without echo function + int 21h + endm +; +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 ends +; +Code segment + assume cs:Code, ds:Data +Start proc near + mov ax,Data + mov ds,ax +; +; Go to hi-res graphics mode. +; + mov ax,10h ;AH = 0 means mode set, AL = 10h selects + ; hi-res graphics mode + int 10h ;BIOS video interrupt +; +; Put up some text, so the screen isn't empty. +; + mov ah,9 ;DOS print string function + mov dx,offset SampleText + int 21h +; + WAIT_KEY +; +; Blank the screen. +; + mov dx,INPUT_STATUS_1 + in al,dx ;reset port 3c0h to index (rather than data) + ; mode + mov dx,AC_INDEX + sub al,al ;make bit 5 zero... + out dx,al ;...which blanks the screen +; + WAIT_KEY +; +; Unblank the screen. +; + mov dx,INPUT_STATUS_1 + in al,dx ;reset port 3c0h to Index (rather than data) + ; mode + mov dx,AC_INDEX + mov al,20h ;make bit 5 one... + out dx,al ;...which unblanks the screen +; + WAIT_KEY +; +; Restore text mode. +; + mov ax,2 + int 10h +; +; Done. +; +Done: + mov ah,4ch ;DOS terminate function + int 21h +Start endp +Code ends + end Start +``` diff --git a/29-06.md b/29-06.md index e73015c..345dd32 100644 --- a/29-06.md +++ b/29-06.md @@ -43,14 +43,16 @@ read the register, mask off the desired field, insert the desired setting, and write the result back to the register. In the case of setting the VGA to write mode 1, do this: - mov dx,3ceh ;Graphics controller index - mov al,5 ;Graphics mode reg index - out dx,al ;point GC index to G_MODE - inc dx ;Graphics controller data - in al,dx ;get current mode setting - and al,not 3 ;mask off write mode field - or al,1 ;set write mode field to 1 - out dx,al ;set write mode 1 +```nasm +mov dx,3ceh ;Graphics controller index +mov al,5 ;Graphics mode reg index +out dx,al ;point GC index to G_MODE +inc dx ;Graphics controller data +in al,dx ;get current mode setting +and al,not 3 ;mask off write mode field +or al,1 ;set write mode field to 1 +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 diff --git a/30-02.md b/30-02.md index e7ff426..d3f1332 100644 --- a/30-02.md +++ b/30-02.md @@ -14,375 +14,377 @@ Finally, after another keypress, Listing 30.1 halts. **LISTING 30.1 L30-1.ASM** - ; Demonstrates the VGA/EGA split screen in action. - ; - ;********************************************************************* - IS_VGA equ 1 ;set to 0 to assemble for EGA - ; - VGA_SEGMENT equ 0a000h - SCREEN_WIDTH equ 640 - SCREEN_HEIGHT equ 350 - CRTC_INDEX equ 3d4h ;CRT Controller Index register - OVERFLOW equ 7 ;index of Overflow reg in CRTC - MAXIMUM_SCAN_LINEequ 9 ;index of Maximum Scan Line register - ; in CRTC - START_ADDRESS_HIGH equ 0ch ;index of Start Address High register - ; in CRTC - START_ADDRESS_LOW equ 0dh ;index of Start Address Low register - ; in CRTC - LINE_COMPARE equ 18h ;index of Line Compare reg (bits 7-0 - ; of split screen start scan line) - ; in CRTC - INPUT_STATUS_0 equ 3dah ;Input Status 0 register - WORD_OUTS_OK equ 1 ;set to 0 to assemble for - ; computers that can't handle - ; word outs to indexed VGA registers - ;********************************************************************* - ; Macro to output a word value to a port. - ; - OUT_WORD macro - if WORD_OUTS_OK - out dx,ax - else - out dx,al - inc dx - xchg ah,al - out dx,al - dec dx - xchg ah,al - endif - endm - ;********************************************************************* - MyStack segment para stack 'STACK' - db 512 dup (0) - MyStack ends - ;********************************************************************* - Data segment - SplitScreenLine dw ? ;line the split screen currently - ; starts after - StartAddress dw ? ;display memory offset at which - ; scanning for video data starts - ; Message displayed in split screen. - SplitScreenMsg db 'Split screen text row #' - DigitInsert dw ? - db '...$' - Data ends - ;********************************************************************* - Code segment - assume cs:Code, ds:Data - ;********************************************************************* - Start proc near - mov ax,Data - mov ds,ax - ; - ; Select mode 10h, 640x350 16-color graphics mode. - ; - mov ax,0010h ;AH=0 is select mode function - ;AL=10h is mode to select, - ; 640x350 16-color graphics mode - int 10h - ; - ; Put text into display memory starting at offset 0, with each row - ; labelled as to number. This is the part of memory that will be - ; displayed in the split screen portion of the display. - ; - mov cx,25 ;# of lines of text we'll draw into - ; the split screen part of memory - FillSplitScreenLoop: - mov ah,2 ;set cursor location function # - sub bh,bh ;set cursor in page 0 - mov dh,25 - sub dh,cl ;calculate row to draw in - sub dl,dl ;start in column 0 - int 10h ;set the cursor location - mov al,25 - sub al,cl ;calculate row to draw in again - sub ah,ah ;make the value a word for division - mov dh,10 - div dh ;split the row # into two digits - add ax,'00' ;convert the digits to ASCII - mov [DigitInsert],ax ;put the digits into the text - ; to be displayed - mov ah,9 - mov dx,offset SplitScreenMsg - int 21h ;print the text - loop FillSplitScreenLoop - ; - ; Fill display memory starting at 8000h with a diagonally striped - ; pattern. - ; - mov ax,VGA_SEGMENT - mov es,ax - mov di,8000h - mov dx,SCREEN_HEIGHT ;fill all lines - mov ax,8888h ;starting fill pattern - cld - RowLoop: - mov cx,SCREEN_WIDTH/8/2 ;fill 1 scan line a word at a time - rep stosw ;fill the scan line - ror ax,1 ;shift pattern word - dec dx - jnz RowLoop - ; - ; Set the start address to 8000h and display that part of memory. - ; - mov [StartAddress],8000h - call SetStartAddress - ; - ; Slide the split screen half way up the screen and then back down - ; a quarter of the screen. - ; - mov [SplitScreenLine],SCREEN_HEIGHT-1 - ;set the initial line just off - ; the bottom of the screen - mov cx,SCREEN_HEIGHT/2 - call SplitScreenUp - mov cx,SCREEN_HEIGHT/4 - call SplitScreenDown - ; - ; Now move up another half a screen and then back down a quarter. - ; - mov cx,SCREEN_HEIGHT/2 - call SplitScreenUp - mov cx,SCREEN_HEIGHT/4 - call SplitScreenDown - ; - ; Finally move up to the top of the screen. - ; - mov cx,SCREEN_HEIGHT/2-2 - call SplitScreenUp - ; - ; Wait for a key press (don't echo character). - ; - mov ah,8 ;DOS console input without echo function - int 21h - ; - ; Turn the split screen off. - ; - mov [SplitScreenLine],0ffffh - call SetSplitScreenScanLine - ; - ; Wait for a key press (don't echo character). - ; - mov ah,8 ;DOS console input without echo function - int 21h - ; - ; Display the memory at 0 (the same memory the split screen displays). - ; - mov [StartAddress],0 - call SetStartAddress - ; - ; Flip between the split screen and the normal screen every 10th - ; frame until a key is pressed. - ; - FlipLoop: - xor [SplitScreenLine],0ffffh - call SetSplitScreenScanLine - mov cx,10 - CountVerticalSyncsLoop: - call WaitForVerticalSyncEnd - loop CountVerticalSyncsLoop - mov ah,0bh ;DOS character available status - int 21h - and al,al ;character available? - jz FlipLoop ;no, toggle split screen on/off status - mov ah,1 - int 21h ;clear the character - ; - ; Return to text mode and DOS. - ; - mov ax,0003h ;AH=0 is select mode function - ;AL=3 is mode to select, text mode - int 10h ;return to text mode - mov ah,4ch - int 21h ;return to DOS - Startendp - ;********************************************************************* - ; Waits for the leading edge of the vertical sync pulse. - ; - ; Input: none - ; - ; Output: none - ; - ; Registers altered: AL, DX - ; - WaitForVerticalSyncStartprocnear - mov dx,INPUT_STATUS_0 - WaitNotVerticalSync: - in al,dx - test al,08h - jnz WaitNotVerticalSync - WaitVerticalSync: - in al,dx - test al,08h - jz WaitVerticalSync - ret - WaitForVerticalSyncStart endp - ;********************************************************************* - ; Waits for the trailing edge of the vertical sync pulse. - ; - ; Input: none - ; - ; Output: none - ; - ; Registers altered: AL, DX - ; - WaitForVerticalSyncEndprocnear - mov dx,INPUT_STATUS_0 - WaitVerticalSync2: - in al,dx - test al,08h - jz WaitVerticalSync2 - WaitNotVerticalSync2: - in al,dx - test al,08h - jnz WaitNotVerticalSync2 - ret - WaitForVerticalSyncEndendp - ;********************************************************************* - ; Sets the start address to the value specifed by StartAddress. - ; Wait for the trailing edge of vertical sync before setting so that - ; one half of the address isn't loaded before the start of the frame - ; and the other half after, resulting in flicker as one frame is - ; displayed with mismatched halves. The new start address won't be - ; loaded until the start of the next frame; that is, one full frame - ; will be displayed before the new start address takes effect. - ; - ; Input: none - ; - ; Output: none - ; - ; Registers altered: AX, DX - ; - SetStartAddress proc near - call WaitForVerticalSyncEnd - mov dx,CRTC_INDEX - mov al,START_ADDRESS_HIGH - mov ah,byte ptr [StartAddress+1] - cli ;make sure both registers get set at once - OUT_WORD - mov al,START_ADDRESS_LOW - mov ah,byte ptr [StartAddress] - OUT_WORD - sti - ret - SetStartAddress endp - ;********************************************************************* - ; Sets the scan line the split screen starts after to the scan line - ; specified by SplitScreenLine. - ; - ; Input: none - ; - ; Output: none - ; - ; All registers preserved - ; - SetSplitScreenScanLine proc near - push ax - push cx - push dx - ; - ; Wait for the leading edge of the vertical sync pulse. This ensures - ; that we don't get mismatched portions of the split screen setting - ; while setting the two or three split screen registers (register 18h - ; set but register 7 not yet set when a match occurs, for example), - ; which could produce brief flickering. - ; - call WaitForVerticalSyncStart - ; - ; Set the split screen scan line. - ; - mov dx,CRTC_INDEX - mov ah,byte ptr [SplitScreenLine] - mov al,LINE_COMPARE - cli ;make sure all the registers get set at once - OUT_WORD ;set bits 7-0 of the split screen scan line - mov ah,byte ptr [SplitScreenLine+1] - and ah,1 - mov cl,4 - shl ah,cl ;move bit 8 of the split split screen scan - ; line into position for the Overflow reg - mov al,OVERFLOW - if IS_VGA - ; - ; The Split Screen, Overflow, and Line Compare registers all contain - ; part of the split screen start scan line on the VGA. We'll take - ; advantage of the readable registers of the VGA to leave other bits - ; in the registers we access undisturbed. - ; - out dx,al ;set CRTC Index reg to point to Overflow - inc dx ;point to CRTC Data reg - in al,dx ;get the current Overflow reg setting - and al,not 10h ;turn off split screen bit 8 - or al,ah ;insert the new split screen bit 8 - ; (works in any mode) - out dx,al ;set the new split screen bit 8 - dec dx ;point to CRTC Index reg - mov ah,byte ptr [SplitScreenLine+1] - and ah,2 - mov cl,3 - ror ah,cl ;move bit 9 of the split split screen scan - ; line into position for the Maximum Scan - ; Line register - mov al,MAXIMUM_SCAN_LINE - out dx,al ;set CRTC Index reg to point to Maximum - ; Scan Line - inc dx ;point to CRTC Data reg - in al,dx ;get the current Maximum Scan Line setting - and al,not 40h ;turn off split screen bit 9 - or al,ah ;insert the new split screen bit 9 - ; (works in any mode) - out dx,al ;set the new split screen bit 9 - else - ; - ; Only the Split Screen and Overflow registers contain part of the - ; Split Screen start scan line and need to be set on the EGA. - ; EGA registers are not readable, so we have to set the non-split - ; screen bits of the Overflow register to a preset value, in this - ; case the value for 350-scan-line modes. - ; - or ah,0fh ;insert the new split screen bit 8 - ; (only works in 350-scan-line EGA modes) - OUT_WORD ;set the new split screen bit 8 - endif - sti - pop dx - pop cx - pop ax - ret - SetSplitScreenScanLine endp - ;********************************************************************* - ; Moves the split screen up the specified number of scan lines. - ; - ; Input: CX = # of scan lines to move the split screen up by - ; - ; Output: none - ; - ; Registers altered: CX - ; - SplitScreenUp proc near - SplitScreenUpLoop: - dec [SplitScreenLine] - call SetSplitScreenScanLine - loop SplitScreenUpLoop - ret - SplitScreenUp endp - ;********************************************************************* - ; Moves the split screen down the specified number of scan lines. - ; - ; Input: CX = # of scan lines to move the split screen down by - ; - ; Output: none - ; - ; Registers altered: CX - ; - SplitScreenDown proc near - SplitScreenDownLoop: - inc [SplitScreenLine] - call SetSplitScreenScanLine - loop SplitScreenDownLoop - ret - SplitScreenDown endp - ;********************************************************************* - Code ends - end Start +```nasm +; Demonstrates the VGA/EGA split screen in action. +; +;********************************************************************* +IS_VGA equ 1 ;set to 0 to assemble for EGA +; +VGA_SEGMENT equ 0a000h +SCREEN_WIDTH equ 640 +SCREEN_HEIGHT equ 350 +CRTC_INDEX equ 3d4h ;CRT Controller Index register +OVERFLOW equ 7 ;index of Overflow reg in CRTC +MAXIMUM_SCAN_LINEequ 9 ;index of Maximum Scan Line register + ; in CRTC +START_ADDRESS_HIGH equ 0ch ;index of Start Address High register + ; in CRTC +START_ADDRESS_LOW equ 0dh ;index of Start Address Low register + ; in CRTC +LINE_COMPARE equ 18h ;index of Line Compare reg (bits 7-0 + ; of split screen start scan line) + ; in CRTC +INPUT_STATUS_0 equ 3dah ;Input Status 0 register +WORD_OUTS_OK equ 1 ;set to 0 to assemble for + ; computers that can't handle + ; word outs to indexed VGA registers +;********************************************************************* +; Macro to output a word value to a port. +; +OUT_WORD macro +if WORD_OUTS_OK + out dx,ax +else + out dx,al + inc dx + xchg ah,al + out dx,al + dec dx + xchg ah,al +endif + endm +;********************************************************************* +MyStack segment para stack 'STACK' + db 512 dup (0) +MyStack ends +;********************************************************************* +Data segment +SplitScreenLine dw ? ;line the split screen currently + ; starts after +StartAddress dw ? ;display memory offset at which + ; scanning for video data starts +; Message displayed in split screen. +SplitScreenMsg db 'Split screen text row #' +DigitInsert dw ? + db '...$' +Data ends +;********************************************************************* +Code segment + assume cs:Code, ds:Data +;********************************************************************* +Start proc near + mov ax,Data + mov ds,ax +; +; Select mode 10h, 640x350 16-color graphics mode. +; + mov ax,0010h ;AH=0 is select mode function + ;AL=10h is mode to select, + ; 640x350 16-color graphics mode + int 10h +; +; Put text into display memory starting at offset 0, with each row +; labelled as to number. This is the part of memory that will be +; displayed in the split screen portion of the display. +; + mov cx,25 ;# of lines of text we'll draw into + ; the split screen part of memory +FillSplitScreenLoop: + mov ah,2 ;set cursor location function # + sub bh,bh ;set cursor in page 0 + mov dh,25 + sub dh,cl ;calculate row to draw in + sub dl,dl ;start in column 0 + int 10h ;set the cursor location + mov al,25 + sub al,cl ;calculate row to draw in again + sub ah,ah ;make the value a word for division + mov dh,10 + div dh ;split the row # into two digits + add ax,'00' ;convert the digits to ASCII + mov [DigitInsert],ax ;put the digits into the text + ; to be displayed + mov ah,9 + mov dx,offset SplitScreenMsg + int 21h ;print the text + loop FillSplitScreenLoop +; +; Fill display memory starting at 8000h with a diagonally striped +; pattern. +; + mov ax,VGA_SEGMENT + mov es,ax + mov di,8000h + mov dx,SCREEN_HEIGHT ;fill all lines + mov ax,8888h ;starting fill pattern + cld +RowLoop: + mov cx,SCREEN_WIDTH/8/2 ;fill 1 scan line a word at a time + rep stosw ;fill the scan line + ror ax,1 ;shift pattern word + dec dx + jnz RowLoop +; +; Set the start address to 8000h and display that part of memory. +; + mov [StartAddress],8000h + call SetStartAddress +; +; Slide the split screen half way up the screen and then back down +; a quarter of the screen. +; + mov [SplitScreenLine],SCREEN_HEIGHT-1 + ;set the initial line just off + ; the bottom of the screen + mov cx,SCREEN_HEIGHT/2 + call SplitScreenUp + mov cx,SCREEN_HEIGHT/4 + call SplitScreenDown +; +; Now move up another half a screen and then back down a quarter. +; + mov cx,SCREEN_HEIGHT/2 + call SplitScreenUp + mov cx,SCREEN_HEIGHT/4 + call SplitScreenDown +; +; Finally move up to the top of the screen. +; + mov cx,SCREEN_HEIGHT/2-2 + call SplitScreenUp +; +; Wait for a key press (don't echo character). +; + mov ah,8 ;DOS console input without echo function + int 21h +; +; Turn the split screen off. +; + mov [SplitScreenLine],0ffffh + call SetSplitScreenScanLine +; +; Wait for a key press (don't echo character). +; + mov ah,8 ;DOS console input without echo function + int 21h +; +; Display the memory at 0 (the same memory the split screen displays). +; + mov [StartAddress],0 + call SetStartAddress +; +; Flip between the split screen and the normal screen every 10th +; frame until a key is pressed. +; +FlipLoop: + xor [SplitScreenLine],0ffffh + call SetSplitScreenScanLine + mov cx,10 +CountVerticalSyncsLoop: + call WaitForVerticalSyncEnd + loop CountVerticalSyncsLoop + mov ah,0bh ;DOS character available status + int 21h + and al,al ;character available? + jz FlipLoop ;no, toggle split screen on/off status + mov ah,1 + int 21h ;clear the character +; +; Return to text mode and DOS. +; + mov ax,0003h ;AH=0 is select mode function +;AL=3 is mode to select, text mode + int 10h ;return to text mode + mov ah,4ch + int 21h ;return to DOS +Startendp +;********************************************************************* +; Waits for the leading edge of the vertical sync pulse. +; +; Input: none +; +; Output: none +; +; Registers altered: AL, DX +; +WaitForVerticalSyncStartprocnear + mov dx,INPUT_STATUS_0 +WaitNotVerticalSync: + in al,dx + test al,08h + jnz WaitNotVerticalSync +WaitVerticalSync: + in al,dx + test al,08h + jz WaitVerticalSync + ret +WaitForVerticalSyncStart endp +;********************************************************************* +; Waits for the trailing edge of the vertical sync pulse. +; +; Input: none +; +; Output: none +; +; Registers altered: AL, DX +; +WaitForVerticalSyncEndprocnear + mov dx,INPUT_STATUS_0 +WaitVerticalSync2: + in al,dx + test al,08h + jz WaitVerticalSync2 +WaitNotVerticalSync2: + in al,dx + test al,08h + jnz WaitNotVerticalSync2 + ret +WaitForVerticalSyncEndendp +;********************************************************************* +; Sets the start address to the value specifed by StartAddress. +; Wait for the trailing edge of vertical sync before setting so that +; one half of the address isn't loaded before the start of the frame +; and the other half after, resulting in flicker as one frame is +; displayed with mismatched halves. The new start address won't be +; loaded until the start of the next frame; that is, one full frame +; will be displayed before the new start address takes effect. +; +; Input: none +; +; Output: none +; +; Registers altered: AX, DX +; +SetStartAddress proc near + call WaitForVerticalSyncEnd + mov dx,CRTC_INDEX + mov al,START_ADDRESS_HIGH + mov ah,byte ptr [StartAddress+1] + cli ;make sure both registers get set at once + OUT_WORD + mov al,START_ADDRESS_LOW + mov ah,byte ptr [StartAddress] + OUT_WORD + sti + ret +SetStartAddress endp +;********************************************************************* +; Sets the scan line the split screen starts after to the scan line +; specified by SplitScreenLine. +; +; Input: none +; +; Output: none +; +; All registers preserved +; +SetSplitScreenScanLine proc near + push ax + push cx + push dx +; +; Wait for the leading edge of the vertical sync pulse. This ensures +; that we don't get mismatched portions of the split screen setting +; while setting the two or three split screen registers (register 18h +; set but register 7 not yet set when a match occurs, for example), +; which could produce brief flickering. +; + call WaitForVerticalSyncStart +; +; Set the split screen scan line. +; + mov dx,CRTC_INDEX + mov ah,byte ptr [SplitScreenLine] + mov al,LINE_COMPARE + cli ;make sure all the registers get set at once + OUT_WORD ;set bits 7-0 of the split screen scan line + mov ah,byte ptr [SplitScreenLine+1] + and ah,1 + mov cl,4 + shl ah,cl ;move bit 8 of the split split screen scan + ; line into position for the Overflow reg + mov al,OVERFLOW +if IS_VGA +; +; The Split Screen, Overflow, and Line Compare registers all contain +; part of the split screen start scan line on the VGA. We'll take +; advantage of the readable registers of the VGA to leave other bits +; in the registers we access undisturbed. +; + out dx,al ;set CRTC Index reg to point to Overflow + inc dx ;point to CRTC Data reg + in al,dx ;get the current Overflow reg setting + and al,not 10h ;turn off split screen bit 8 + or al,ah ;insert the new split screen bit 8 + ; (works in any mode) + out dx,al ;set the new split screen bit 8 + dec dx ;point to CRTC Index reg + mov ah,byte ptr [SplitScreenLine+1] + and ah,2 + mov cl,3 + ror ah,cl ;move bit 9 of the split split screen scan + ; line into position for the Maximum Scan + ; Line register + mov al,MAXIMUM_SCAN_LINE + out dx,al ;set CRTC Index reg to point to Maximum + ; Scan Line + inc dx ;point to CRTC Data reg + in al,dx ;get the current Maximum Scan Line setting + and al,not 40h ;turn off split screen bit 9 + or al,ah ;insert the new split screen bit 9 + ; (works in any mode) + out dx,al ;set the new split screen bit 9 +else +; +; Only the Split Screen and Overflow registers contain part of the +; Split Screen start scan line and need to be set on the EGA. +; EGA registers are not readable, so we have to set the non-split +; screen bits of the Overflow register to a preset value, in this +; case the value for 350-scan-line modes. +; + or ah,0fh ;insert the new split screen bit 8 + ; (only works in 350-scan-line EGA modes) + OUT_WORD ;set the new split screen bit 8 +endif + sti + pop dx + pop cx + pop ax + ret +SetSplitScreenScanLine endp +;********************************************************************* +; Moves the split screen up the specified number of scan lines. +; +; Input: CX = # of scan lines to move the split screen up by +; +; Output: none +; +; Registers altered: CX +; +SplitScreenUp proc near +SplitScreenUpLoop: + dec [SplitScreenLine] + call SetSplitScreenScanLine + loop SplitScreenUpLoop + ret +SplitScreenUp endp +;********************************************************************* +; Moves the split screen down the specified number of scan lines. +; +; Input: CX = # of scan lines to move the split screen down by +; +; Output: none +; +; Registers altered: CX +; +SplitScreenDown proc near +SplitScreenDownLoop: + inc [SplitScreenLine] + call SetSplitScreenScanLine + loop SplitScreenDownLoop + ret +SplitScreenDown endp +;********************************************************************* +Code ends + end Start +``` diff --git a/30-05.md b/30-05.md index 6fd8ad2..31fa20f 100644 --- a/30-05.md +++ b/30-05.md @@ -12,421 +12,423 @@ pages: 575-582 **LISTING 30.2 L30-2.ASM** - ; Demonstrates the interaction of the split screen and - ; horizontal pel panning. On a VGA, first pans right in the top - ; half while the split screen jerks around, because split screen - ; pel panning suppression is disabled, then enables split screen - ; pel panning suppression and pans right in the top half while the - ; split screen remains stable. On an EGA, the split screen jerks - ; around in both cases, because the EGA doesn't support split - ; screen pel panning suppression. - ; - ; The jerking in the split screen occurs because the split screen - ; is being pel panned (panned by single pixels--intrabyte panning), - ; but is not and cannot be byte panned (panned by single bytes-- - ; "extrabyte" panning) because the start address of the split screen - ; is forever fixed at 0. - ;********************************************************************* - IS_VGA equ 1 ;set to 0 to assemble for EGA - ; - VGA_SEGMENT equ 0a000h - LOGICAL_SCREEN_WIDTH equ 1024 ;# of pixels across virtual - ; screen that we'll pan across - SCREEN_HEIGHT equ 350 - SPLIT_SCREEN_START equ 200 ;start scan line for split screen - SPLIT_SCREEN_HEIGHT equ SCREEN_HEIGHT-SPLIT_SCREEN_START-1 - CRTC_INDEX equ 3d4h ;CRT Controller Index register - AC_INDEX equ 3c0h ;Attribute Controller Index reg - OVERFLOW equ 7 ;index of Overflow reg in CRTC - MAXIMUM_SCAN_LINE equ 9 ;index of Maximum Scan Line register - ; in CRTC - START_ADDRESS_HIGH equ 0ch ;index of Start Address High register - ; in CRTC - START_ADDRESS_LOW equ 0dh ;index of Start Address Low register - ; in CRTC - HOFFSET equ 13h ;index of Horizontal Offset register - ; in CRTC - LINE_COMPARE equ 18h ;index of Line Compare reg (bits 7-0 - ; of split screen start scan line) - ; in CRTC - AC_MODE_CONTROL equ 10h ;index of Mode Control reg in AC - PEL_PANNING equ 13h ;index of Pel Panning reg in AC - INPUT_STATUS_0 equ 3dah ;Input Status 0 register - WORD_OUTS_OK equ 1 ;set to 0 to assemble for - ; computers that can't handle - ; word outs to indexed VGA registers - ;********************************************************************* - ; Macro to output a word value to a port. - ; - OUT_WORD macro - if WORD_OUTS_OK - out dx,ax - else - out dx,al - inc dx - xchg ah,al - out dx,al - dec dx - xchg ah,al - endif - endm - ;********************************************************************* - MyStack segment para stack 'STACK' - db 512 dup (0) - MyStack ends - ;********************************************************************* - Datasegment - SplitScreenLine dw ? ;line the split screen currently - ; starts after - StartAddress dw ? ;display memory offset at which - ; scanning for video data starts - PelPan db ? ;current intrabyte horizontal pel - ; panning setting - Data ends - ;********************************************************************* - Code segment - assume cs:Code, ds:Data - ;********************************************************************* - Startproc near - mov ax,Data - mov ds,ax - ; - ; Select mode 10h, 640x350 16-color graphics mode. - ; - mov ax,0010h ;AH=0 is select mode function - ;AL=10h is mode to select, - ; 640x350 16-color graphics mode - int 10h - ; - ; Set the Offset register to make the offset from the start of one - ; scan line to the start of the next the desired number of pixels. - ; This gives us a virtual screen wider than the actual screen to - ; pan across. - ; Note that the Offset register is programmed with the logical - ; screen width in words, not bytes, hence the final division by 2. - ; - mov dx,CRTC_INDEX - mov ax,(LOGICAL_SCREEN_WIDTH/8/2 shl 8) or HOFFSET - OUT_WORD - ; - ; Set the start address to display the memory just past the split - ; screen memory. - ; - mov [StartAddress],SPLIT_SCREEN_HEIGHT*(LOGICAL_SCREEN_WIDTH/8) - call SetStartAddress - ; - ; Set the split screen start scan line. - ; - mov [SplitScreenLine],SPLIT_SCREEN_START - call SetSplitScreenScanLine - ; - ; Fill the split screen portion of display memory (starting at - ; offset 0) with a choppy diagonal pattern sloping left. - ; - mov ax,VGA_SEGMENT - mov es,ax - sub di,di - mov dx,SPLIT_SCREEN_HEIGHT - ;fill all lines in the split screen - mov ax,0FF0h ;starting fill pattern - cld - RowLoop: - mov cx,LOGICAL_SCREEN_WIDTH/8/4 - ;fill 1 scan line - ColumnLoop: - sto sw ;draw part of a diagonal line - mov word ptr es:[di],0 ;make vertical blank spaces so - ; panning effects can be seen easily - inc di - inc di - loop ColumnLoop - rol ax,1 ;shift pattern word - dec dx - jnz RowLoop - ; - ; Fill the portion of display memory that will be displayed in the - ; normal screen (the non-split screen part of the display) with a - ; choppy diagonal pattern sloping right. - ; - mov di,SPLIT_SCREEN_HEIGHT*(LOGICAL_SCREEN_WIDTH/8) - mov dx,SCREEN_HEIGHT ;fill all lines - mov ax,0c510h ;starting fill pattern - cld - RowLoop2: - mov cx,LOGICAL_SCREEN_WIDTH/8/4 - ;fill 1 scan line - ColumnLoop2: - sto sw ;draw part of a diagonal line - mov word ptr es:[di],0 ;make vertical blank spaces so - ; panning effects can be seen easily - inc di - inc di - loopColumnLoop2 - ror ax,1 ;shift pattern word - dec dx - jnz RowLoop2 - ; - ; Pel pan the non-split screen portion of the display; because - ; split screen pel panning suppression is not turned on, the split - ; screen jerks back and forth as the pel panning setting cycles. - ; - mov cx,200 ;pan 200 pixels to the left - callPanRight - ; - ; Wait for a key press (don't echo character). - ; - mov ah,8 ;DOS console input without echo function - int 21h - ; - ; Return to the original screen location, with pel panning turned off. - ; - mov [StartAddress],SPLIT_SCREEN_HEIGHT*(LOGICAL_SCREEN_WIDTH/8) - call SetStartAddress - mov [PelPan],0 - call SetPelPan - ; - ; Turn on split screen pel panning suppression, so the split screen - ; won't be affected by pel panning. Not done on EGA because both - ; readable registers and the split screen pel panning suppression bit - ; aren't supported by EGAs. - ; - if IS_VGA - mov dx,INPUT_STATUS_0 - in al,dx ;reset the AC Index/Data toggle to - ; Index state - mov al,20h+AC_MODE_CONTROL - ;bit 5 set to 1 to keep video on - mov dx,AC_INDEX ;point to AC Index/Data register - out dx,al - inc dx ;point to AC Data reg (for reads only) - in al,dx ;get the current AC Mode Control reg - or al,20h ;enable split screen pel panning - ; suppression - dec dx ;point to AC Index/Data reg (Data for - ; writes only) - out dx,al ;write the new AC Mode Control setting - ; with split screen pel panning - ; suppression turned on - endif - ; - ; Pel pan the non-split screen portion of the display; because - ; split screen pel panning suppression is turned on, the split - ; screen will not move as the pel panning setting cycles. - ; - mov cx,200 ;pan 200 pixels to the left - call PanRight - ; - ; Wait for a key press (don't echo character). - ; - mov ah,8 ;DOS console input without echo function - int 21h - ; - ; Return to text mode and DOS. - ; - mov ax,0003h ;AH=0 is select mode function - ;AL=3 is mode to select, text mode - int 10h ;return to text mode - mov ah,4ch - int 21h ;return to DOS - Startendp - ;********************************************************************* - ; Waits for the leading edge of the vertical sync pulse. - ; - ; Input: none - ; - ; Output: none - ; - ; Registers altered: AL, DX - ; - WaitForVerticalSyncStart proc near - mov dx,INPUT_STATUS_0 - WaitNotVerticalSync: - in al,dx - test al,08h - jnz WaitNotVerticalSync - WaitVerticalSync: - in al,dx - test al,08h - jz WaitVerticalSync - ret - WaitForVerticalSyncStart endp - ;********************************************************************* - ; Waits for the trailing edge of the vertical sync pulse. - ; - ; Input: none - ; - ; Output: none - ; - ; Registers altered: AL, DX - ; - WaitForVerticalSyncEnd proc near - mov dx,INPUT_STATUS_0 - WaitVerticalSync2: - in al,dx - test al,08h - jz WaitVerticalSync2 - WaitNotVerticalSync2: - in al,dx - test al,08h - jnz WaitNotVerticalSync2 - ret - WaitForVerticalSyncEnd endp - ;********************************************************************* - ; Sets the start address to the value specifed by StartAddress. - ; Wait for the trailing edge of vertical sync before setting so that - ; one half of the address isn't loaded before the start of the frame - ; and the other half after, resulting in flicker as one frame is - ; displayed with mismatched halves. The new start address won't be - ; loaded until the start of the next frame; that is, one full frame - ; will be displayed before the new start address takes effect. - ; - ; Input: none - ; - ; Output: none - ; - ; Registers altered: AX, DX - ; - SetStartAddress proc near - call WaitForVerticalSyncEnd - mov dx,CRTC_INDEX - mov al,START_ADDRESS_HIGH - mov ah,byte ptr [StartAddress+1] - cli ;make sure both registers get set at once - OUT_WORD - mov al,START_ADDRESS_LOW - mov ah,byte ptr [StartAddress] - OUT_WORD - sti - ret - SetStartAddress endp - ;********************************************************************* - ; Sets the horizontal pel panning setting to the value specified - ; by PelPan. Waits until the start of vertical sync to do so, so - ; the new pel pan setting can be loaded during non-display time - ; and can be ready by the start of the next frame. - ; - ; Input: none - ; - ; Output: none - ; - ; Registers altered: AL, DX - ; - SetPelPan proc near - call WaitForVerticalSyncStart ;also resets the AC - ; Index/Data toggle - ; to Index state - mov dx,AC_INDEX - mov al,PEL_PANNING+20h ;bit 5 set to 1 to keep video on - out dx,al ;point the AC Index to Pel Pan reg - mov al,[PelPan] - out dx,al ;load the new Pel Pan setting - ret - SetPelPanendp - ;********************************************************************* - ; Sets the scan line the split screen starts after to the scan line - ; specified by SplitScreenLine. - ; - ; Input: none - ; - ; Output: none - ; - ; All registers preserved - ; - SetSplitScreenScanLine proc near - push ax - push cx - push dx - ; - ; Wait for the leading edge of the vertical sync pulse. This ensures - ; that we don't get mismatched portions of the split screen setting - ; while setting the two or three split screen registers (register 18h - ; set but register 7 not yet set when a match occurs, for example), - ; which could produce brief flickering. - ; - call WaitForVerticalSyncStart - ; - ; Set the split screen scan line. - ; - mov dx,CRTC_INDEX - mov ah,byte ptr [SplitScreenLine] - mov al,LINE_COMPARE - cli ;make sure all the registers get set at once - OUT_WORD ;set bits 7-0 of the split screen scan line - mov ah,byte ptr [SplitScreenLine+1] - and ah,1 - mov cl,4 - shl ah,cl ;move bit 8 of the split split screen scan - ; line into position for the Overflow reg - mov al,OVERFLOW - if IS_VGA - ; - ; The Split Screen, Overflow, and Line Compare registers all contain - ; part of the split screen start scan line on the VGA. We'll take - ; advantage of the readable registers of the VGA to leave other bits - ; in the registers we access undisturbed. - ; - out dx,al ;set CRTC Index reg to point to Overflow - inc dx ;point to CRTC Data reg - in al,dx ;get the current Overflow reg setting - and al,not 10h ;turn off split screen bit 8 - or al,ah ;insert the new split screen bit 8 - ; (works in any mode) - out dx,al ;set the new split screen bit 8 - dec dx ;point to CRTC Index reg - mov ah,byte ptr [SplitScreenLine+1] - and ah,2 - mov cl,3 - ror ah,cl ;move bit 9 of the split split screen scan - ; line into position for the Maximum Scan - ; Line register - mov al,MAXIMUM_SCAN_LINE - out dx,al ;set CRTC Index reg to point to Maximum - ; Scan Line - inc dx ;point to CRTC Data reg - in al,dx ;get the current Maximum Scan Line setting - and al,not 40h ;turn off split screen bit 9 - or al,ah ;insert the new split screen bit 9 - ; (works in any mode) - out dx,al ;set the new split screen bit 9 - else - ; - ; Only the Split Screen and Overflow registers contain part of the - ; Split Screen start scan line and need to be set on the EGA. - ; EGA registers are not readable, so we have to set the non-split - ; screen bits of the Overflow register to a preset value, in this - ; case the value for 350-scan-line modes. - ; - or ah,0fh ;insert the new split screen bit 8 - ; (only works in 350-scan-line EGA modes) - OUT_WORD ;set the new split screen bit 8 - endif - sti - pop dx - pop cx - pop ax - ret - SetSplitScreenScanLine endp - ;********************************************************************* - ; Pan horizontally to the right the number of pixels specified by CX. - ; - ; Input: CX = # of pixels by which to pan horizontally - ; - ; Output: none - ; - ; Registers altered: AX, CX, DX - ; - PanRight proc near - PanLoop: - inc [PelPan] - and [PelPan],07h - jnz DoSetStartAddress - inc [StartAddress] - DoSetStartAddress: - call SetStartAddress - call SetPelPan - loop PanLoop - ret - PanRight endp - ;********************************************************************* - Codeends - endStart +```nasm +; Demonstrates the interaction of the split screen and +; horizontal pel panning. On a VGA, first pans right in the top +; half while the split screen jerks around, because split screen +; pel panning suppression is disabled, then enables split screen +; pel panning suppression and pans right in the top half while the +; split screen remains stable. On an EGA, the split screen jerks +; around in both cases, because the EGA doesn't support split +; screen pel panning suppression. +; +; The jerking in the split screen occurs because the split screen +; is being pel panned (panned by single pixels--intrabyte panning), +; but is not and cannot be byte panned (panned by single bytes-- +; "extrabyte" panning) because the start address of the split screen +; is forever fixed at 0. +;********************************************************************* +IS_VGA equ 1 ;set to 0 to assemble for EGA +; +VGA_SEGMENT equ 0a000h +LOGICAL_SCREEN_WIDTH equ 1024 ;# of pixels across virtual + ; screen that we'll pan across +SCREEN_HEIGHT equ 350 +SPLIT_SCREEN_START equ 200 ;start scan line for split screen +SPLIT_SCREEN_HEIGHT equ SCREEN_HEIGHT-SPLIT_SCREEN_START-1 +CRTC_INDEX equ 3d4h ;CRT Controller Index register +AC_INDEX equ 3c0h ;Attribute Controller Index reg +OVERFLOW equ 7 ;index of Overflow reg in CRTC +MAXIMUM_SCAN_LINE equ 9 ;index of Maximum Scan Line register + ; in CRTC +START_ADDRESS_HIGH equ 0ch ;index of Start Address High register + ; in CRTC +START_ADDRESS_LOW equ 0dh ;index of Start Address Low register + ; in CRTC +HOFFSET equ 13h ;index of Horizontal Offset register + ; in CRTC +LINE_COMPARE equ 18h ;index of Line Compare reg (bits 7-0 + ; of split screen start scan line) + ; in CRTC +AC_MODE_CONTROL equ 10h ;index of Mode Control reg in AC +PEL_PANNING equ 13h ;index of Pel Panning reg in AC +INPUT_STATUS_0 equ 3dah ;Input Status 0 register +WORD_OUTS_OK equ 1 ;set to 0 to assemble for + ; computers that can't handle + ; word outs to indexed VGA registers +;********************************************************************* +; Macro to output a word value to a port. +; +OUT_WORD macro +if WORD_OUTS_OK + out dx,ax +else + out dx,al + inc dx + xchg ah,al + out dx,al + dec dx + xchg ah,al +endif + endm +;********************************************************************* +MyStack segment para stack 'STACK' + db 512 dup (0) +MyStack ends +;********************************************************************* +Datasegment +SplitScreenLine dw ? ;line the split screen currently + ; starts after +StartAddress dw ? ;display memory offset at which + ; scanning for video data starts +PelPan db ? ;current intrabyte horizontal pel + ; panning setting +Data ends +;********************************************************************* +Code segment + assume cs:Code, ds:Data +;********************************************************************* +Startproc near + mov ax,Data + mov ds,ax +; +; Select mode 10h, 640x350 16-color graphics mode. +; + mov ax,0010h ;AH=0 is select mode function + ;AL=10h is mode to select, + ; 640x350 16-color graphics mode + int 10h +; +; Set the Offset register to make the offset from the start of one +; scan line to the start of the next the desired number of pixels. +; This gives us a virtual screen wider than the actual screen to +; pan across. +; Note that the Offset register is programmed with the logical +; screen width in words, not bytes, hence the final division by 2. +; + mov dx,CRTC_INDEX + mov ax,(LOGICAL_SCREEN_WIDTH/8/2 shl 8) or HOFFSET + OUT_WORD +; +; Set the start address to display the memory just past the split +; screen memory. +; + mov [StartAddress],SPLIT_SCREEN_HEIGHT*(LOGICAL_SCREEN_WIDTH/8) + call SetStartAddress +; +; Set the split screen start scan line. +; + mov [SplitScreenLine],SPLIT_SCREEN_START + call SetSplitScreenScanLine +; +; Fill the split screen portion of display memory (starting at +; offset 0) with a choppy diagonal pattern sloping left. +; + mov ax,VGA_SEGMENT + mov es,ax + sub di,di + mov dx,SPLIT_SCREEN_HEIGHT + ;fill all lines in the split screen + mov ax,0FF0h ;starting fill pattern + cld +RowLoop: + mov cx,LOGICAL_SCREEN_WIDTH/8/4 + ;fill 1 scan line +ColumnLoop: + sto sw ;draw part of a diagonal line + mov word ptr es:[di],0 ;make vertical blank spaces so + ; panning effects can be seen easily + inc di + inc di + loop ColumnLoop + rol ax,1 ;shift pattern word + dec dx + jnz RowLoop +; +; Fill the portion of display memory that will be displayed in the +; normal screen (the non-split screen part of the display) with a +; choppy diagonal pattern sloping right. +; + mov di,SPLIT_SCREEN_HEIGHT*(LOGICAL_SCREEN_WIDTH/8) + mov dx,SCREEN_HEIGHT ;fill all lines + mov ax,0c510h ;starting fill pattern + cld +RowLoop2: + mov cx,LOGICAL_SCREEN_WIDTH/8/4 + ;fill 1 scan line +ColumnLoop2: + sto sw ;draw part of a diagonal line + mov word ptr es:[di],0 ;make vertical blank spaces so + ; panning effects can be seen easily + inc di + inc di +loopColumnLoop2 + ror ax,1 ;shift pattern word + dec dx + jnz RowLoop2 +; +; Pel pan the non-split screen portion of the display; because +; split screen pel panning suppression is not turned on, the split +; screen jerks back and forth as the pel panning setting cycles. +; + mov cx,200 ;pan 200 pixels to the left +callPanRight +; +; Wait for a key press (don't echo character). +; + mov ah,8 ;DOS console input without echo function + int 21h +; +; Return to the original screen location, with pel panning turned off. +; + mov [StartAddress],SPLIT_SCREEN_HEIGHT*(LOGICAL_SCREEN_WIDTH/8) + call SetStartAddress + mov [PelPan],0 + call SetPelPan +; +; Turn on split screen pel panning suppression, so the split screen +; won't be affected by pel panning. Not done on EGA because both +; readable registers and the split screen pel panning suppression bit +; aren't supported by EGAs. +; +if IS_VGA + mov dx,INPUT_STATUS_0 + in al,dx ;reset the AC Index/Data toggle to + ; Index state + mov al,20h+AC_MODE_CONTROL + ;bit 5 set to 1 to keep video on + mov dx,AC_INDEX ;point to AC Index/Data register + out dx,al + inc dx ;point to AC Data reg (for reads only) + in al,dx ;get the current AC Mode Control reg + or al,20h ;enable split screen pel panning + ; suppression + dec dx ;point to AC Index/Data reg (Data for + ; writes only) + out dx,al ;write the new AC Mode Control setting + ; with split screen pel panning + ; suppression turned on +endif +; +; Pel pan the non-split screen portion of the display; because +; split screen pel panning suppression is turned on, the split +; screen will not move as the pel panning setting cycles. +; + mov cx,200 ;pan 200 pixels to the left + call PanRight +; +; Wait for a key press (don't echo character). +; + mov ah,8 ;DOS console input without echo function + int 21h +; +; Return to text mode and DOS. +; + mov ax,0003h ;AH=0 is select mode function + ;AL=3 is mode to select, text mode + int 10h ;return to text mode + mov ah,4ch + int 21h ;return to DOS +Startendp +;********************************************************************* +; Waits for the leading edge of the vertical sync pulse. +; +; Input: none +; +; Output: none +; +; Registers altered: AL, DX +; +WaitForVerticalSyncStart proc near + mov dx,INPUT_STATUS_0 +WaitNotVerticalSync: + in al,dx + test al,08h + jnz WaitNotVerticalSync +WaitVerticalSync: + in al,dx + test al,08h + jz WaitVerticalSync + ret +WaitForVerticalSyncStart endp +;********************************************************************* +; Waits for the trailing edge of the vertical sync pulse. +; +; Input: none +; +; Output: none +; +; Registers altered: AL, DX +; +WaitForVerticalSyncEnd proc near + mov dx,INPUT_STATUS_0 +WaitVerticalSync2: + in al,dx + test al,08h + jz WaitVerticalSync2 +WaitNotVerticalSync2: + in al,dx + test al,08h + jnz WaitNotVerticalSync2 + ret +WaitForVerticalSyncEnd endp +;********************************************************************* +; Sets the start address to the value specifed by StartAddress. +; Wait for the trailing edge of vertical sync before setting so that +; one half of the address isn't loaded before the start of the frame +; and the other half after, resulting in flicker as one frame is +; displayed with mismatched halves. The new start address won't be +; loaded until the start of the next frame; that is, one full frame +; will be displayed before the new start address takes effect. +; +; Input: none +; +; Output: none +; +; Registers altered: AX, DX +; +SetStartAddress proc near + call WaitForVerticalSyncEnd + mov dx,CRTC_INDEX + mov al,START_ADDRESS_HIGH + mov ah,byte ptr [StartAddress+1] + cli ;make sure both registers get set at once + OUT_WORD + mov al,START_ADDRESS_LOW + mov ah,byte ptr [StartAddress] + OUT_WORD + sti + ret +SetStartAddress endp +;********************************************************************* +; Sets the horizontal pel panning setting to the value specified +; by PelPan. Waits until the start of vertical sync to do so, so +; the new pel pan setting can be loaded during non-display time +; and can be ready by the start of the next frame. +; +; Input: none +; +; Output: none +; +; Registers altered: AL, DX +; +SetPelPan proc near + call WaitForVerticalSyncStart ;also resets the AC + ; Index/Data toggle + ; to Index state + mov dx,AC_INDEX + mov al,PEL_PANNING+20h ;bit 5 set to 1 to keep video on + out dx,al ;point the AC Index to Pel Pan reg + mov al,[PelPan] + out dx,al ;load the new Pel Pan setting + ret +SetPelPanendp +;********************************************************************* +; Sets the scan line the split screen starts after to the scan line +; specified by SplitScreenLine. +; +; Input: none +; +; Output: none +; +; All registers preserved +; +SetSplitScreenScanLine proc near + push ax + push cx + push dx +; +; Wait for the leading edge of the vertical sync pulse. This ensures +; that we don't get mismatched portions of the split screen setting +; while setting the two or three split screen registers (register 18h +; set but register 7 not yet set when a match occurs, for example), +; which could produce brief flickering. +; + call WaitForVerticalSyncStart +; +; Set the split screen scan line. +; + mov dx,CRTC_INDEX + mov ah,byte ptr [SplitScreenLine] + mov al,LINE_COMPARE + cli ;make sure all the registers get set at once + OUT_WORD ;set bits 7-0 of the split screen scan line + mov ah,byte ptr [SplitScreenLine+1] + and ah,1 + mov cl,4 + shl ah,cl ;move bit 8 of the split split screen scan +; line into position for the Overflow reg + mov al,OVERFLOW +if IS_VGA +; +; The Split Screen, Overflow, and Line Compare registers all contain +; part of the split screen start scan line on the VGA. We'll take +; advantage of the readable registers of the VGA to leave other bits +; in the registers we access undisturbed. +; + out dx,al ;set CRTC Index reg to point to Overflow + inc dx ;point to CRTC Data reg + in al,dx ;get the current Overflow reg setting + and al,not 10h ;turn off split screen bit 8 + or al,ah ;insert the new split screen bit 8 + ; (works in any mode) + out dx,al ;set the new split screen bit 8 + dec dx ;point to CRTC Index reg + mov ah,byte ptr [SplitScreenLine+1] + and ah,2 + mov cl,3 + ror ah,cl ;move bit 9 of the split split screen scan + ; line into position for the Maximum Scan + ; Line register + mov al,MAXIMUM_SCAN_LINE + out dx,al ;set CRTC Index reg to point to Maximum + ; Scan Line + inc dx ;point to CRTC Data reg + in al,dx ;get the current Maximum Scan Line setting + and al,not 40h ;turn off split screen bit 9 + or al,ah ;insert the new split screen bit 9 + ; (works in any mode) + out dx,al ;set the new split screen bit 9 +else +; +; Only the Split Screen and Overflow registers contain part of the +; Split Screen start scan line and need to be set on the EGA. +; EGA registers are not readable, so we have to set the non-split +; screen bits of the Overflow register to a preset value, in this +; case the value for 350-scan-line modes. +; + or ah,0fh ;insert the new split screen bit 8 + ; (only works in 350-scan-line EGA modes) + OUT_WORD ;set the new split screen bit 8 +endif + sti + pop dx + pop cx + pop ax + ret +SetSplitScreenScanLine endp +;********************************************************************* +; Pan horizontally to the right the number of pixels specified by CX. +; +; Input: CX = # of pixels by which to pan horizontally +; +; Output: none +; +; Registers altered: AX, CX, DX +; +PanRight proc near +PanLoop: + inc [PelPan] + and [PelPan],07h + jnz DoSetStartAddress + inc [StartAddress] +DoSetStartAddress: + call SetStartAddress + call SetPelPan + loop PanLoop + ret +PanRight endp +;********************************************************************* +Codeends +endStart +``` diff --git a/31-03.md b/31-03.md index 7bef705..cb93ebb 100644 --- a/31-03.md +++ b/31-03.md @@ -12,330 +12,332 @@ pages: 593-598 **LISTING 31.1 L31-1.ASM** - ; Program to demonstrate pixel drawing in 320x400 256-color - ; mode on the VGA. Draws 8 lines to form an octagon, a pixel - ; at a time. Draws 8 octagons in all, one on top of the other, - ; each in a different color set. Although it's not used, a - ; pixel read function is also provided. - ; - VGA_SEGMENT equ 0a000h - SC_INDEX equ 3c4h ;Sequence Controller Index register - GC_INDEX equ 3ceh ;Graphics Controller Index register - CRTC_INDEX equ 3d4h ;CRT Controller Index register - MAP_MASK equ 2 ;Map Mask register index in SC - MEMORY_MODE equ 4 ;Memory Mode register index in SC - MAX_SCAN_LINE equ 9 ;Maximum Scan Line reg index in CRTC - START_ADDRESS_HIGH equ 0ch ;Start Address High reg index in CRTC - UNDERLINE equ 14h ;Underline Location reg index in CRTC - MODE_CONTROL equ 17h ;Mode Control register index in CRTC - READ_MAP equ 4 ;Read Map register index in GC - GRAPHICS_MODE equ 5 ;Graphics Mode register index in GC - MISCELLANEOUS equ 6 ;Miscellaneous register index in GC - SCREEN_WIDTH equ 320 ;# of pixels across screen - SCREEN_HEIGHT equ 400 ;# of scan lines on screen - WORD_OUTS_OK equ 1 ;set to 0 to assemble for - ; computers that can't handle - ; word outs to indexed VGA registers - ; - stack segment para stack 'STACK' - db 512 dup (?) - stack ends - ; - Data segment word 'DATA' - ; - BaseColor db 0 - ; - ; Structure used to control drawing of a line. - ; - LineControl struc - StartX dw ? - StartY dw ? - LineXInc dw ? - LineYInc dw ? - BaseLength dw ? - LineColor db ? - LineControl ends - ; - ; List of descriptors for lines to draw. - ; - LineList label LineControl - LineControl <130,110,1,0,60,0> - LineControl <190,110,1,1,60,1> - LineControl <250,170,0,1,60,2> - LineControl <250,230,-1,1,60,3> - LineControl <190,290,-1,0,60,4> - LineControl <130,290,-1,-1,60,5> - LineControl <70,230,0,-1,60,6> - LineControl <70,170,1,-1,60,7> - LineControl <-1,0,0,0,0,0> - Data ends - ; - ; Macro to output a word value to a port. - ; - OUT_WORD macro - if WORD_OUTS_OK - out dx,ax - else - out dx,al - inc dx - xchg ah,al - out dx,al - dec dx - xchg ah,al - endif - endm - ; - ; Macro to output a constant value to an indexed VGA register. - ; - CONSTANT_TO_INDEXED_REGISTERmacroADDRESS, INDEX, VALUE - mov dx,ADDRESS - mov ax,(VALUE shl 8) + INDEX - OUT_WORD - endm - ; - Code segment - assume cs:Code, ds:Data - Start proc near - mov ax,Data - mov ds,ax - ; +```nasm +; Program to demonstrate pixel drawing in 320x400 256-color +; mode on the VGA. Draws 8 lines to form an octagon, a pixel +; at a time. Draws 8 octagons in all, one on top of the other, +; each in a different color set. Although it's not used, a +; pixel read function is also provided. +; +VGA_SEGMENT equ 0a000h +SC_INDEX equ 3c4h ;Sequence Controller Index register +GC_INDEX equ 3ceh ;Graphics Controller Index register +CRTC_INDEX equ 3d4h ;CRT Controller Index register +MAP_MASK equ 2 ;Map Mask register index in SC +MEMORY_MODE equ 4 ;Memory Mode register index in SC +MAX_SCAN_LINE equ 9 ;Maximum Scan Line reg index in CRTC +START_ADDRESS_HIGH equ 0ch ;Start Address High reg index in CRTC +UNDERLINE equ 14h ;Underline Location reg index in CRTC +MODE_CONTROL equ 17h ;Mode Control register index in CRTC +READ_MAP equ 4 ;Read Map register index in GC +GRAPHICS_MODE equ 5 ;Graphics Mode register index in GC +MISCELLANEOUS equ 6 ;Miscellaneous register index in GC +SCREEN_WIDTH equ 320 ;# of pixels across screen +SCREEN_HEIGHT equ 400 ;# of scan lines on screen +WORD_OUTS_OK equ 1 ;set to 0 to assemble for + ; computers that can't handle + ; word outs to indexed VGA registers +; +stack segment para stack 'STACK' + db 512 dup (?) +stack ends +; +Data segment word 'DATA' +; +BaseColor db 0 +; +; Structure used to control drawing of a line. +; +LineControl struc +StartX dw ? +StartY dw ? +LineXInc dw ? +LineYInc dw ? +BaseLength dw ? +LineColor db ? +LineControl ends +; +; List of descriptors for lines to draw. +; +LineList label LineControl + LineControl <130,110,1,0,60,0> + LineControl <190,110,1,1,60,1> + LineControl <250,170,0,1,60,2> + LineControl <250,230,-1,1,60,3> + LineControl <190,290,-1,0,60,4> + LineControl <130,290,-1,-1,60,5> + LineControl <70,230,0,-1,60,6> + LineControl <70,170,1,-1,60,7> + LineControl <-1,0,0,0,0,0> +Data ends +; +; Macro to output a word value to a port. +; +OUT_WORD macro +if WORD_OUTS_OK + out dx,ax +else + out dx,al + inc dx + xchg ah,al + out dx,al + dec dx + xchg ah,al +endif + endm +; +; Macro to output a constant value to an indexed VGA register. +; +CONSTANT_TO_INDEXED_REGISTERmacroADDRESS, INDEX, VALUE + mov dx,ADDRESS + mov ax,(VALUE shl 8) + INDEX + OUT_WORD + endm +; +Code segment + assume cs:Code, ds:Data +Start proc near + mov ax,Data + mov ds,ax +; - ; Set 320x400 256-color mode. - ; - call Set320By400Mode - ; - ; We're in 320x400 256-color mode. Draw each line in turn. - ; - ColorLoop: - mov si,offset LineList ;point to the start of the - ; line descriptor list - LineLoop: - mov cx,[si+StartX] ;set the initial X coordinate - cmpcx,-1 - jz LinesDone ;a descriptor with a -1 X - ; coordinate marks the end - ; of the list - mov dx,[si+StartY] ;set the initial Y coordinate, - mov bl,[si+LineColor] ; line color, - mov bp,[si+BaseLength] ; and pixel count - add bl,[BaseColor] ;adjust the line color according - ; to BaseColor - PixelLoop: - push cx ;save the coordinates - push dx - call WritePixel ;draw this pixel - pop dx ;retrieve the coordinates - pop cx - add cx,[si+LineXInc] ;set the coordinates of the - add dx,[si+LineYInc] ; next point of the line - dec bp ;any more points? - jnz PixelLoop ;yes, draw the next - add si,size LineControl ;point to the next line descriptor - jmp LineLoop ; and draw the next line - LinesDone: - call GetNextKey ;wait for a key, then - inc [BaseColor] ; bump the color selection and - cmp [BaseColor],8 ; see if we're done - jb ColorLoop ;not done yet - ; - ; Wait for a key and return to text mode and end when - ; one is pressed. - ; - call GetNextKey - mov ax,0003h - int 10h text mode - mov ah,4ch - int 21h ;done - ; - Start endp - ; - ; Sets up 320x400 256-color modes. - ; - ; Input: none - ; - ; Output: none - ; - Set320By400Mode proc near - ; - ; First, go to normal 320x200 256-color mode, which is really a - ; 320x400 256-color mode with each line scanned twice. - ; - mov ax,0013h ;AH = 0 means mode set, AL = 13h selects - ; 256-color graphics mode - int 10h ;BIOS video interrupt - ; - ; Change CPU addressing of video memory to linear (not odd/even, - ; chain, or chain 4), to allow us to access all 256K of display - ; memory. When this is done, VGA memory will look just like memory - ; in modes 10h and 12h, except that each byte of display memory will - ; control one 256-color pixel, with 4 adjacent pixels at any given - ; address, one pixel per plane. - ; - mov dx,SC_INDEX - mov al,MEMORY_MODE - out dx,al - inc dx - ina l,dx - and al,not 08h ;turn off chain 4 - ora l,04h ;turn off odd/even - out dx,al - mov dx,GC_INDEX - mov al,GRAPHICS_MODE - out dx,al - inc dx - ina l,dx - and al,not 10h ;turn off odd/even - out dx,al - dec dx - mov al,MISCELLANEOUS - out dx,al - inc dx - ina l,dx - and al,not 02h ;turn off chain - out dx,al - ; - ; Now clear the whole screen, since the mode 13h mode set only - ; cleared 64K out of the 256K of display memory. Do this before - ; we switch the CRTC out of mode 13h, so we don't see garbage - ; on the screen when we make the switch. - ; - CONSTANT_TO_INDEXED_REGISTER SC_INDEX,MAP_MASK,0fh - ;enable writes to all planes, so - ; we can clear 4 pixels at a time - mov ax,VGA_SEGMENT - mov es,ax - sub di,di - mov ax,di - mov cx,8000h ;# of words in 64K - cld - rep stosw ;clear all of display memory - ; - ; Tweak the mode to 320x400 256-color mode by not scanning each - ; line twice. - ; - mov dx,CRTC_INDEX - mov al,MAX_SCAN_LINE - out dx,al - inc dx - in al,dx - and al,not 1fh ;set maximum scan line = 0 - out dx,al - dec dx - ; - ; Change CRTC scanning from doubleword mode to byte mode, allowing - ; the CRTC to scan more than 64K of video data. - ; - mov al,UNDERLINE - out dx,al - inc dx - in al,dx - and al,not 40h ;turn off doubleword - out dx,al - dec dx - mov al,MODE_CONTROL - out dx,al - inc dx - in al,dx - or al,40h ;turn on the byte mode bit, so memory is - ; scanned for video data in a purely - ; linear way, just as in modes 10h and 12h - out dx,al - ret - Set320By400Mode endp - ; - ; Draws a pixel in the specified color at the specified - ; location in 320x400 256-color mode. - ; - ; Input: - ; CX = X coordinate of pixel - ; DX = Y coordinate of pixel - ; BL = pixel color - ; - ; Output: none - ; - ; Registers altered: AX, CX, DX, DI, ES - ; - WritePixel proc near - mov ax,VGA_SEGMENT - mov es,ax ;point to display memory - mov ax,SCREEN_WIDTH/4 - ;there are 4 pixels at each address, so - ; each 320-pixel row is 80 bytes wide - ; in each plane - mul dx ;point to start of desired row - push cx ;set aside the X coordinate - shr cx,1 ;there are 4 pixels at each address - shr cx,1 ; so divide the X coordinate by 4 - add ax,cx ;point to the pixel's address - mov di,ax - pop cx ;get back the X coordinate - and cl,3 ;get the plane # of the pixel - mov ah,1 - shl ah,cl ;set the bit corresponding to the plane - ; the pixel is in - mov al,MAP_MASK - mov dx,SC_INDEX - OUT_WORD ;set to write to the proper plane for - ; the pixel - mov es:[di],bl ;draw the pixel - ret - WritePixelendp - ; - ; Reads the color of the pixel at the specified location in 320x400 - ; 256-color mode. - ; - ; Input: - ; CX = X coordinate of pixel to read - ; DX = Y coordinate of pixel to read - ; - ; Output: - ; AL = pixel color - ; - ; Registers altered: AX, CX, DX, SI, ES - ; - ReadPixelprocnear - mov ax,VGA_SEGMENT - mov es,ax ;point to display memory - mov ax,SCREEN_WIDTH/4 - ;there are 4 pixels at each address, so - ; each 320-pixel row is 80 bytes wide - ; in each plane - mul dx ;point to start of desired row - push cx ;set aside the X coordinate - shr cx,1 ;there are 4 pixels at each address - shr cx,1 ; so divide the X coordinate by 4 - add ax,cx ;point to the pixel's address - mov si,ax - pop ax ;get back the X coordinate - and al,3 ;get the plane # of the pixel - mov ah,al - mov al,READ_MAP - mov dx,GC_INDEX - OUT_WORD ;set to read from the proper plane for - ; the pixel - lodsbyte ptr es:[si] ;read the pixel - ret - ReadPixelendp - ; - ; Waits for the next key and returns it in AX. - ; - ; Input: none - ; - ; Output: - ; AX = full 16-bit code for key pressed - ; - GetNextKey proc near - WaitKey: - mov ah,1 - int 16h - jz WaitKey ;wait for a key to become available - sub ah,ah - int 16h ;read the key - ret - GetNextKey endp - ; - Code ends - ; - end Start +; Set 320x400 256-color mode. +; + call Set320By400Mode +; +; We're in 320x400 256-color mode. Draw each line in turn. +; +ColorLoop: + mov si,offset LineList ;point to the start of the + ; line descriptor list +LineLoop: + mov cx,[si+StartX] ;set the initial X coordinate + cmpcx,-1 + jz LinesDone ;a descriptor with a -1 X + ; coordinate marks the end + ; of the list + mov dx,[si+StartY] ;set the initial Y coordinate, + mov bl,[si+LineColor] ; line color, + mov bp,[si+BaseLength] ; and pixel count + add bl,[BaseColor] ;adjust the line color according + ; to BaseColor +PixelLoop: + push cx ;save the coordinates + push dx + call WritePixel ;draw this pixel + pop dx ;retrieve the coordinates + pop cx + add cx,[si+LineXInc] ;set the coordinates of the + add dx,[si+LineYInc] ; next point of the line + dec bp ;any more points? + jnz PixelLoop ;yes, draw the next + add si,size LineControl ;point to the next line descriptor + jmp LineLoop ; and draw the next line +LinesDone: + call GetNextKey ;wait for a key, then + inc [BaseColor] ; bump the color selection and + cmp [BaseColor],8 ; see if we're done + jb ColorLoop ;not done yet +; +; Wait for a key and return to text mode and end when +; one is pressed. +; + call GetNextKey + mov ax,0003h + int 10h text mode + mov ah,4ch + int 21h ;done +; +Start endp +; +; Sets up 320x400 256-color modes. +; +; Input: none +; +; Output: none +; +Set320By400Mode proc near +; +; First, go to normal 320x200 256-color mode, which is really a +; 320x400 256-color mode with each line scanned twice. +; + mov ax,0013h ;AH = 0 means mode set, AL = 13h selects + ; 256-color graphics mode + int 10h ;BIOS video interrupt +; +; Change CPU addressing of video memory to linear (not odd/even, +; chain, or chain 4), to allow us to access all 256K of display +; memory. When this is done, VGA memory will look just like memory +; in modes 10h and 12h, except that each byte of display memory will +; control one 256-color pixel, with 4 adjacent pixels at any given +; address, one pixel per plane. +; + mov dx,SC_INDEX + mov al,MEMORY_MODE + out dx,al + inc dx + ina l,dx + and al,not 08h ;turn off chain 4 + ora l,04h ;turn off odd/even + out dx,al + mov dx,GC_INDEX + mov al,GRAPHICS_MODE + out dx,al + inc dx + ina l,dx + and al,not 10h ;turn off odd/even + out dx,al + dec dx + mov al,MISCELLANEOUS + out dx,al + inc dx + ina l,dx + and al,not 02h ;turn off chain + out dx,al +; +; Now clear the whole screen, since the mode 13h mode set only +; cleared 64K out of the 256K of display memory. Do this before +; we switch the CRTC out of mode 13h, so we don't see garbage +; on the screen when we make the switch. +; +CONSTANT_TO_INDEXED_REGISTER SC_INDEX,MAP_MASK,0fh + ;enable writes to all planes, so + ; we can clear 4 pixels at a time + mov ax,VGA_SEGMENT + mov es,ax + sub di,di + mov ax,di + mov cx,8000h ;# of words in 64K + cld + rep stosw ;clear all of display memory +; +; Tweak the mode to 320x400 256-color mode by not scanning each +; line twice. +; + mov dx,CRTC_INDEX + mov al,MAX_SCAN_LINE + out dx,al + inc dx + in al,dx + and al,not 1fh ;set maximum scan line = 0 + out dx,al + dec dx +; +; Change CRTC scanning from doubleword mode to byte mode, allowing +; the CRTC to scan more than 64K of video data. +; + mov al,UNDERLINE + out dx,al + inc dx + in al,dx + and al,not 40h ;turn off doubleword + out dx,al + dec dx + mov al,MODE_CONTROL + out dx,al + inc dx + in al,dx + or al,40h ;turn on the byte mode bit, so memory is + ; scanned for video data in a purely + ; linear way, just as in modes 10h and 12h + out dx,al + ret +Set320By400Mode endp +; +; Draws a pixel in the specified color at the specified +; location in 320x400 256-color mode. +; +; Input: +; CX = X coordinate of pixel +; DX = Y coordinate of pixel +; BL = pixel color +; +; Output: none +; +; Registers altered: AX, CX, DX, DI, ES +; +WritePixel proc near + mov ax,VGA_SEGMENT + mov es,ax ;point to display memory + mov ax,SCREEN_WIDTH/4 + ;there are 4 pixels at each address, so + ; each 320-pixel row is 80 bytes wide + ; in each plane + mul dx ;point to start of desired row + push cx ;set aside the X coordinate + shr cx,1 ;there are 4 pixels at each address + shr cx,1 ; so divide the X coordinate by 4 + add ax,cx ;point to the pixel's address + mov di,ax + pop cx ;get back the X coordinate + and cl,3 ;get the plane # of the pixel + mov ah,1 + shl ah,cl ;set the bit corresponding to the plane + ; the pixel is in + mov al,MAP_MASK + mov dx,SC_INDEX + OUT_WORD ;set to write to the proper plane for + ; the pixel + mov es:[di],bl ;draw the pixel + ret +WritePixelendp +; +; Reads the color of the pixel at the specified location in 320x400 +; 256-color mode. +; +; Input: +; CX = X coordinate of pixel to read +; DX = Y coordinate of pixel to read +; +; Output: +; AL = pixel color +; +; Registers altered: AX, CX, DX, SI, ES +; +ReadPixelprocnear + mov ax,VGA_SEGMENT + mov es,ax ;point to display memory + mov ax,SCREEN_WIDTH/4 + ;there are 4 pixels at each address, so + ; each 320-pixel row is 80 bytes wide + ; in each plane + mul dx ;point to start of desired row + push cx ;set aside the X coordinate + shr cx,1 ;there are 4 pixels at each address + shr cx,1 ; so divide the X coordinate by 4 + add ax,cx ;point to the pixel's address + mov si,ax + pop ax ;get back the X coordinate + and al,3 ;get the plane # of the pixel + mov ah,al + mov al,READ_MAP + mov dx,GC_INDEX + OUT_WORD ;set to read from the proper plane for + ; the pixel + lodsbyte ptr es:[si] ;read the pixel + ret +ReadPixelendp +; +; Waits for the next key and returns it in AX. +; +; Input: none +; +; Output: +; AX = full 16-bit code for key pressed +; +GetNextKey proc near +WaitKey: + mov ah,1 + int 16h + jz WaitKey ;wait for a key to become available + sub ah,ah + int 16h ;read the key + ret +GetNextKey endp +; +Code ends +; +end Start +``` diff --git a/31-05.md b/31-05.md index 8b3ce65..feb50d0 100644 --- a/31-05.md +++ b/31-05.md @@ -12,265 +12,267 @@ pages: 600-605 **LISTING 31.2 L31-2.ASM** - ; Program to demonstrate the two pages available in 320x400 - ; 256-color modes on a VGA. Draws diagonal color bars in all - ; 256 colors in page 0, then does the same in page 1 (but with - ; the bars tilted the other way), and finally draws vertical - ; color bars in page 0. - ; - VGA_SEGMENT equ 0a000h - SC_INDEX equ 3c4h ;Sequence Controller Index register - GC_INDEX equ 3ceh ;Graphics Controller Index register - CRTC_INDEX equ 3d4h ;CRT Controller Index register - MAP_MASK equ 2 ;Map Mask register index in SC - MEMORY_MODE equ 4 ;Memory Mode register index in SC - MAX_SCAN_LINE equ 9 ;Maximum Scan Line reg index in CRTC - START_ADDRESS_HIGH equ 0ch ;Start Address High reg index in CRTC - UNDERLINE equ 14h ;Underline Location reg index in CRTC - MODE_CONTROL equ 17h ;Mode Control register index in CRTC - GRAPHICS_MODE equ 5 ;Graphics Mode register index in GC - MISCELLANEOUS equ 6 ;Miscellaneous register index in GC - SCREEN_WIDTH equ 320 ;# of pixels across screen - SCREEN_HEIGHT equ 400 ;# of scan lines on screen - WORD_OUTS_OK equ 1 ;set to 0 to assemble for - ; computers that can't handle - ; word outs to indexed VGA registers - ; - stack segment para stack 'STACK' - db 512 dup (?) - stack ends - ; - ; Macro to output a word value to a port. - ; - OUT_WORDmacro - if WORD_OUTS_OK - outdx,ax - else - out dx,al - inc dx - xch gah,al - out dx,al - dec dx - xch gah,al - endif - endm - ; - ; Macro to output a constant value to an indexed VGA register. - ; - CONSTANT_TO_INDEXED_REGISTERmacroADDRESS, INDEX, VALUE - mov dx,ADDRESS - mov ax,(VALUE shl 8) + INDEX - OUT_WORD - endm - ; - Code segment - assume cs:Code - Start proc near - ; - ; Set 320x400 256-color mode. - ; - callSet320By400Mode - ; - ; We're in 320x400 256-color mode, with page 0 displayed. - ; Let's fill page 0 with color bars slanting down and to the right. - ; - sub di,di ;page 0 starts at address 0 - mov bl,1 ;make color bars slant down and - ; to the right - call ColorBarsUp ;draw the color bars - ; - ; Now do the same for page 1, but with the color bars - ; tilting the other way. - ; - mov di,8000h ;page 1 starts at address 8000h - mov bl,-1 ;make color bars slant down and - ; to the left - call ColorBarsUp ;draw the color bars - ; - ; Wait for a key and flip to page 1 when one is pressed. - ; - callGetNextKey - CONSTANT_TO_INDEXED_REGISTER CRTC_INDEX,START_ADDRESS_HIGH,80h - ;set the Start Address High register - ; to 80h, for a start address of 8000h - ; - ; Draw vertical bars in page 0 while page 1 is displayed. - ; - sub di,di ;page 0 starts at address 0 - sub bl,bl ;make color bars vertical - call ColorBarsUp ;draw the color bars - ; - ; Wait for another key and flip back to page 0 when one is pressed. - ; - callGetNextKey - CONSTANT_TO_INDEXED_REGISTER CRTC_INDEX,START_ADDRESS_HIGH,00h - ;set the Start Address High register - ; to 00h, for a start address of 0000h - ; - ; Wait for yet another key and return to text mode and end when - ; one is pressed. - ; - call GetNextKey - mov ax,0003h - int 10h ;text mode - mov ah,4ch - int 21h ;done - ; - Start endp - ; - ; Sets up 320x400 256-color modes. - ; - ; Input: none - ; - ; Output: none - ; - Set320By400Modeprocnear - ; - ; First, go to normal 320x200 256-color mode, which is really a - ; 320x400 256-color mode with each line scanned twice. - ; - mov ax,0013h ;AH = 0 means mode set, AL = 13h selects - ; 256-color graphics mode - int 10h ;BIOS video interrupt - ; - ; Change CPU addressing of video memory to linear (not odd/even, - ; chain, or chain 4), to allow us to access all 256K of display - ; memory. When this is done, VGA memory will look just like memory - ; in modes 10h and 12h, except that each byte of display memory will - ; control one 256-color pixel, with 4 adjacent pixels at any given - ; address, one pixel per plane. - ; - mov dx,SC_INDEX - mov al,MEMORY_MODE - out dx,al - inc dx - ina l,dx - and al,not 08h ;turn off chain 4 - ora l,04h ;turn off odd/even - out dx,al - mov dx,GC_INDEX - mov al,GRAPHICS_MODE - out dx,al - inc dx - ina l,dx - and al,not 10h ;turn off odd/even - out dx,al - dec dx - mov al,MISCELLANEOUS - out dx,al - inc dx - ina l,dx - and al,not 02h ;turn off chain - out dx,al - ; - ; Now clear the whole screen, since the mode 13h mode set only - ; cleared 64K out of the 256K of display memory. Do this before - ; we switch the CRTC out of mode 13h, so we don't see garbage - ; on the screen when we make the switch. - ; - CONSTANT_TO_INDEXED_REGISTER SC_INDEX,MAP_MASK,0fh - ; enable writes to all planes, so - ; we can clear 4 pixels at a time - mov ax,VGA_SEGMENT - mov es,ax - sub di,di - mov ax,di - mov cx,8000h ;# of words in 64K - cld - rep stosw ;clear all of display memory - ; - ; Tweak the mode to 320x400 256-color mode by not scanning each - ; line twice. - ; - mov dx,CRTC_INDEX - mov al,MAX_SCAN_LINE - out dx,al - inc dx - in al,dx - and al,not 1fh ;set maximum scan line = 0 - out dx,al - dec dx - ; - ; Change CRTC scanning from doubleword mode to byte mode, allowing - ; the CRTC to scan more than 64K of video data. - ; - mov al,UNDERLINE - out dx,al - inc dx - ina l,dx - and al,not40h ;turn off doubleword - out dx,al - dec dx - mov al,MODE_CONTROL - out dx,al - inc dx - in al,dx - or al,40h ;turn on the byte mode bit, so memory is - ; scanned for video data in a purely - ; linear way, just as in modes 10h and 12h - out dx,al - ret - Set320By400Mode endp - ; - ; Draws a full screen of slanting color bars in the specified page. - ; - ; Input: - ; DI = page start address - ; BL = 1 to make the bars slant down and to the right, -1 to - ; make them slant down and to the left, 0 to make - ; them vertical. - ; - ColorBarsUpprocnear - mov ax,VGA_SEGMENT - mov es,ax ;point to display memory - sub bh,bh ;start with color 0 - mov si,SCREEN_HEIGHT ;# of rows to do - mov dx,SC_INDEX - mov al,MAP_MASK - out dx,al ;point the SC Index reg to the Map Mask reg - inc dx ;point DX to the SC Data register - RowLoop: - mov cx,SCREEN_WIDTH/4 - ;4 pixels at each address, so - ; each 320-pixel row is 80 bytes wide - ; in each plane - pus h bx ;save the row-start color - ColumnLoop: - MAP_SELECT = 1 - rept 4 ;do all 4 pixels at this address with - ; in-line code - mov al,MAP_SELECT - out dx,al ;select planes 0, 1, 2, and 3 in turn - mov es:[di],bh ;write this plane's pixel - inc bh ;set the color for the next pixel - MAP_SELECT = MAP_SELECT shl 1 - endm - inc di ;point to the address containing the next - ; 4 pixels - loop ColumnLoop ;do any remaining pixels on this line - pop bx ;get back the row-start color - add bh,bl ;select next row-start color (controls - ; slanting of color bars) - dec si ;count down lines on the screen - jnz RowLoop - ret - ColorBarsUpendp - ; - ; Waits for the next key and returns it in AX. - ; - GetNextKeyprocnear - WaitKey: - mov ah,1 - int 16h - jz WaitKey ;wait for a key to become available - sub ah,ah - int 16h ;read the key - ret - GetNextKey endp - ; - Codeends - ; - endStart +```nasm +; Program to demonstrate the two pages available in 320x400 +; 256-color modes on a VGA. Draws diagonal color bars in all +; 256 colors in page 0, then does the same in page 1 (but with +; the bars tilted the other way), and finally draws vertical +; color bars in page 0. +; +VGA_SEGMENT equ 0a000h +SC_INDEX equ 3c4h ;Sequence Controller Index register +GC_INDEX equ 3ceh ;Graphics Controller Index register +CRTC_INDEX equ 3d4h ;CRT Controller Index register +MAP_MASK equ 2 ;Map Mask register index in SC +MEMORY_MODE equ 4 ;Memory Mode register index in SC +MAX_SCAN_LINE equ 9 ;Maximum Scan Line reg index in CRTC +START_ADDRESS_HIGH equ 0ch ;Start Address High reg index in CRTC +UNDERLINE equ 14h ;Underline Location reg index in CRTC +MODE_CONTROL equ 17h ;Mode Control register index in CRTC +GRAPHICS_MODE equ 5 ;Graphics Mode register index in GC +MISCELLANEOUS equ 6 ;Miscellaneous register index in GC +SCREEN_WIDTH equ 320 ;# of pixels across screen +SCREEN_HEIGHT equ 400 ;# of scan lines on screen +WORD_OUTS_OK equ 1 ;set to 0 to assemble for + ; computers that can't handle + ; word outs to indexed VGA registers +; +stack segment para stack 'STACK' + db 512 dup (?) +stack ends +; +; Macro to output a word value to a port. +; +OUT_WORDmacro +if WORD_OUTS_OK + outdx,ax +else + out dx,al + inc dx + xch gah,al + out dx,al + dec dx + xch gah,al +endif + endm +; +; Macro to output a constant value to an indexed VGA register. +; +CONSTANT_TO_INDEXED_REGISTERmacroADDRESS, INDEX, VALUE + mov dx,ADDRESS + mov ax,(VALUE shl 8) + INDEX + OUT_WORD + endm +; +Code segment + assume cs:Code +Start proc near +; +; Set 320x400 256-color mode. +; +callSet320By400Mode +; +; We're in 320x400 256-color mode, with page 0 displayed. +; Let's fill page 0 with color bars slanting down and to the right. +; + sub di,di ;page 0 starts at address 0 + mov bl,1 ;make color bars slant down and + ; to the right + call ColorBarsUp ;draw the color bars +; +; Now do the same for page 1, but with the color bars +; tilting the other way. +; + mov di,8000h ;page 1 starts at address 8000h + mov bl,-1 ;make color bars slant down and + ; to the left + call ColorBarsUp ;draw the color bars +; +; Wait for a key and flip to page 1 when one is pressed. +; + callGetNextKey + CONSTANT_TO_INDEXED_REGISTER CRTC_INDEX,START_ADDRESS_HIGH,80h + ;set the Start Address High register + ; to 80h, for a start address of 8000h +; +; Draw vertical bars in page 0 while page 1 is displayed. +; + sub di,di ;page 0 starts at address 0 + sub bl,bl ;make color bars vertical + call ColorBarsUp ;draw the color bars +; +; Wait for another key and flip back to page 0 when one is pressed. +; + callGetNextKey + CONSTANT_TO_INDEXED_REGISTER CRTC_INDEX,START_ADDRESS_HIGH,00h + ;set the Start Address High register + ; to 00h, for a start address of 0000h +; +; Wait for yet another key and return to text mode and end when +; one is pressed. +; + call GetNextKey + mov ax,0003h + int 10h ;text mode + mov ah,4ch + int 21h ;done +; +Start endp +; +; Sets up 320x400 256-color modes. +; +; Input: none +; +; Output: none +; +Set320By400Modeprocnear +; +; First, go to normal 320x200 256-color mode, which is really a +; 320x400 256-color mode with each line scanned twice. +; + mov ax,0013h ;AH = 0 means mode set, AL = 13h selects + ; 256-color graphics mode + int 10h ;BIOS video interrupt +; +; Change CPU addressing of video memory to linear (not odd/even, +; chain, or chain 4), to allow us to access all 256K of display +; memory. When this is done, VGA memory will look just like memory +; in modes 10h and 12h, except that each byte of display memory will +; control one 256-color pixel, with 4 adjacent pixels at any given +; address, one pixel per plane. +; + mov dx,SC_INDEX + mov al,MEMORY_MODE + out dx,al + inc dx + ina l,dx + and al,not 08h ;turn off chain 4 + ora l,04h ;turn off odd/even + out dx,al + mov dx,GC_INDEX + mov al,GRAPHICS_MODE + out dx,al + inc dx + ina l,dx + and al,not 10h ;turn off odd/even + out dx,al + dec dx + mov al,MISCELLANEOUS + out dx,al + inc dx + ina l,dx + and al,not 02h ;turn off chain + out dx,al +; +; Now clear the whole screen, since the mode 13h mode set only +; cleared 64K out of the 256K of display memory. Do this before +; we switch the CRTC out of mode 13h, so we don't see garbage +; on the screen when we make the switch. +; +CONSTANT_TO_INDEXED_REGISTER SC_INDEX,MAP_MASK,0fh + ; enable writes to all planes, so + ; we can clear 4 pixels at a time + mov ax,VGA_SEGMENT + mov es,ax + sub di,di + mov ax,di + mov cx,8000h ;# of words in 64K + cld + rep stosw ;clear all of display memory +; +; Tweak the mode to 320x400 256-color mode by not scanning each +; line twice. +; + mov dx,CRTC_INDEX + mov al,MAX_SCAN_LINE + out dx,al + inc dx + in al,dx + and al,not 1fh ;set maximum scan line = 0 + out dx,al + dec dx +; +; Change CRTC scanning from doubleword mode to byte mode, allowing +; the CRTC to scan more than 64K of video data. +; + mov al,UNDERLINE + out dx,al + inc dx + ina l,dx + and al,not40h ;turn off doubleword + out dx,al + dec dx + mov al,MODE_CONTROL + out dx,al + inc dx + in al,dx + or al,40h ;turn on the byte mode bit, so memory is + ; scanned for video data in a purely + ; linear way, just as in modes 10h and 12h + out dx,al + ret +Set320By400Mode endp +; +; Draws a full screen of slanting color bars in the specified page. +; +; Input: +; DI = page start address +; BL = 1 to make the bars slant down and to the right, -1 to +; make them slant down and to the left, 0 to make +; them vertical. +; +ColorBarsUpprocnear + mov ax,VGA_SEGMENT + mov es,ax ;point to display memory + sub bh,bh ;start with color 0 + mov si,SCREEN_HEIGHT ;# of rows to do + mov dx,SC_INDEX + mov al,MAP_MASK + out dx,al ;point the SC Index reg to the Map Mask reg + inc dx ;point DX to the SC Data register +RowLoop: + mov cx,SCREEN_WIDTH/4 + ;4 pixels at each address, so + ; each 320-pixel row is 80 bytes wide + ; in each plane + pus h bx ;save the row-start color +ColumnLoop: +MAP_SELECT = 1 + rept 4 ;do all 4 pixels at this address with + ; in-line code + mov al,MAP_SELECT + out dx,al ;select planes 0, 1, 2, and 3 in turn + mov es:[di],bh ;write this plane's pixel + inc bh ;set the color for the next pixel +MAP_SELECT = MAP_SELECT shl 1 + endm + inc di ;point to the address containing the next + ; 4 pixels + loop ColumnLoop ;do any remaining pixels on this line + pop bx ;get back the row-start color + add bh,bl ;select next row-start color (controls + ; slanting of color bars) + dec si ;count down lines on the screen + jnz RowLoop + ret +ColorBarsUpendp +; +; Waits for the next key and returns it in AX. +; +GetNextKeyprocnear +WaitKey: + mov ah,1 + int 16h + jz WaitKey ;wait for a key to become available + sub ah,ah + int 16h ;read the key + ret +GetNextKey endp +; +Codeends +; +endStart +``` When you run Listing 31.2, note the extremely smooth edges and fine gradations of color, especially in the screens with slanting color bars. diff --git a/32-02.md b/32-02.md index 90bd274..2fa171b 100644 --- a/32-02.md +++ b/32-02.md @@ -12,214 +12,216 @@ pages: 611-615 **LISTING 32.1 L32-1.ASM** - ; Borland C/C++ tiny/small/medium model-callable assembler - ; subroutines to: - ;* Set 360x480 256-color VGA mode - ;* Draw a dot in 360x480 256-color VGA mode - ;* Read the color of a dot in 360x480 256-color VGA mode - ; - ; Assembled with TASM - ; - ; The 360x480 256-color mode set code and parameters were provided - ; by John Bridges, who has placed them into the public domain. - ; - VGA_SEGMENT equ 0a000h ;display memory segment - SC_INDEX equ 3c4h ;Sequence Controller Index register - GC_INDEX equ 3ceh ;Graphics Controller Index register - MAP_MASK equ 2 ;Map Mask register index in SC - 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 - ; word outs to indexed VGA registers - ; - _DATAsegmentpublic byte ‘DATA' - ; - ; 360x480 256-color mode CRT Controller register settings. - ; (Courtesy of John Bridges.) - ; - vptbl dw 06b00h ; horz total - dw 05901h ; horz displayed - dw 05a02h ; start horz blanking - dw 08e03h ; end horz blanking - dw 05e04h ; start h sync - dw 08a05h ; end h sync - dw 00d06h ; vertical total - dw 03e07h ; overflow - dw 04009h ; cell height - dw 0ea10h ; v sync start - dw 0ac11h ; v sync end and protect cr0-cr7 - dw 0df12h ; vertical displayed - dw 02d13h ; offset - dw 00014h ; turn off dword mode - dw 0e715h ; v blank start - dw 00616h ; v blank end - dw 0e317h ; turn on byte mode - vpend label word - _DATAends - ; - ; Macro to output a word value to a port. - ; - OUT_WORDmacro - if WORD_OUTS_OK - out dx,ax - else - out dx,al - inc dx - xchg ah,al - out dx,al - dec dx - xchg ah,al - endif - endm - ; - _TEXTsegment byte public ‘CODE' - assumecs:_TEXT, ds:_DATA - ; - ; Sets up 360x480 256-color mode. - ; (Courtesy of John Bridges.) - ; - ; Call as: void Set360By480Mode() - ; - ; Returns: nothing - ; - public _Set360x480Mode - _Set360x480Modeprocnear - push si ;preserve C register vars - push di - mov ax,12h ; start with mode 12h - int 10h ; let the BIOS clear the video memory +```nasm +; Borland C/C++ tiny/small/medium model-callable assembler +; subroutines to: +;* Set 360x480 256-color VGA mode +;* Draw a dot in 360x480 256-color VGA mode +;* Read the color of a dot in 360x480 256-color VGA mode +; +; Assembled with TASM +; +; The 360x480 256-color mode set code and parameters were provided +; by John Bridges, who has placed them into the public domain. +; +VGA_SEGMENT equ 0a000h ;display memory segment +SC_INDEX equ 3c4h ;Sequence Controller Index register +GC_INDEX equ 3ceh ;Graphics Controller Index register +MAP_MASK equ 2 ;Map Mask register index in SC +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 + ; word outs to indexed VGA registers +; +_DATAsegmentpublic byte ‘DATA' +; +; 360x480 256-color mode CRT Controller register settings. +; (Courtesy of John Bridges.) +; +vptbl dw 06b00h ; horz total + dw 05901h ; horz displayed + dw 05a02h ; start horz blanking + dw 08e03h ; end horz blanking + dw 05e04h ; start h sync + dw 08a05h ; end h sync + dw 00d06h ; vertical total + dw 03e07h ; overflow + dw 04009h ; cell height + dw 0ea10h ; v sync start + dw 0ac11h ; v sync end and protect cr0-cr7 + dw 0df12h ; vertical displayed + dw 02d13h ; offset + dw 00014h ; turn off dword mode + dw 0e715h ; v blank start + dw 00616h ; v blank end + dw 0e317h ; turn on byte mode +vpend label word +_DATAends +; +; Macro to output a word value to a port. +; +OUT_WORDmacro +if WORD_OUTS_OK + out dx,ax +else + out dx,al + inc dx + xchg ah,al + out dx,al + dec dx + xchg ah,al +endif + endm +; +_TEXTsegment byte public ‘CODE' + assumecs:_TEXT, ds:_DATA +; +; Sets up 360x480 256-color mode. +; (Courtesy of John Bridges.) +; +; Call as: void Set360By480Mode() +; +; Returns: nothing +; + public _Set360x480Mode +_Set360x480Modeprocnear + push si ;preserve C register vars + push di + mov ax,12h ; start with mode 12h + int 10h ; let the BIOS clear the video memory - mov ax,13h ; start with standard mode 13h - int 10h ; let the BIOS set the mode + mov ax,13h ; start with standard mode 13h + int 10h ; let the BIOS set the mode - mov dx,3c4h ; alter sequencer registers - mov ax,0604h ; disable chain 4 - out dx,ax + mov dx,3c4h ; alter sequencer registers + mov ax,0604h ; disable chain 4 + out dx,ax - mov ax,0100h ; synchronous reset - out dx,ax ; asserted - mov dx,3c2h ; misc output - mov al,0e7h ; use 28 mHz dot clock - out dx,al ; select it - mov dx,3c4h ; sequencer again - mov ax,0300h ; restart sequencer - out dx,ax ; running again + mov ax,0100h ; synchronous reset + out dx,ax ; asserted + mov dx,3c2h ; misc output + mov al,0e7h ; use 28 mHz dot clock + out dx,al ; select it + mov dx,3c4h ; sequencer again + mov ax,0300h ; restart sequencer + out dx,ax ; running again - mov dx,3d4h ; alter crtc registers + mov dx,3d4h ; alter crtc registers - mov al,11h ; cr11 - out dx,al ; current value - inc dx ; point to data - in al,dx ; get cr11 value - and al,7fh ; remove cr0 -> cr7 - out dx,al ; write protect - dec dx ; point to index - cld - mov si,offset vptbl - mov cx,((offset vpend)-(offset vptbl)) shr 1 - @b: lodsw - out dx,ax - loop @b - pop di ;restore C register vars - pop si - ret - _Set360x480Modeendp - ; - ; Draws a pixel in the specified color at the specified - ; location in 360x480 256-color mode. - ; - ; Call as: void Draw360x480Dot(int X, int Y, int Color) - ; - ; Returns: nothing - ; - DParms struc - dw ? ;pushed BP - dw ? ;return address - DrawX dw ? ;X coordinate at which to draw - DrawY dw ? ;Y coordinate at which to draw - Color dw ? ;color in which to draw (in the - ; range 0-255; upper byte ignored) - DParms ends - ; - public _Draw360x480Dot - _Draw360x480Dotprocnear - push bp ;preserve caller's BP - mov bp,sp ;point to stack frame - push si ;preserve C register vars - push di - mov ax,VGA_SEGMENT - mov es,ax ;point to display memory - mov ax,SCREEN_WIDTH/4 - ;there are 4 pixels at each address, so - ; each 360-pixel row is 90 bytes wide - ; in each plane - mul [bp+DrawY] ;point to start of desired row - 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 - mov cl,byte ptr [bp+DrawX] ;get the X coordinate again - and cl,3 ;get the plane # of the pixel - mov ah,1 - shl ah,cl ;set the bit corresponding to the plane - ; the pixel is in - mov al,MAP_MASK - mov dx,SC_INDEX - OUT_WORD ;set to write to the proper plane for - ; the pixel - mov al,byte ptr [bp+Color] ;get the color - stosb ;draw the pixel - pop di ;restore C register vars - pop si - pop bp ;restore caller's BP - ret - _Draw360x480Dotendp - ; - ; Reads the color of the pixel at the specified - ; location in 360x480 256-color mode. - ; - ; Call as: int Read360x480Dot(int X, int Y) - ; - ; Returns: pixel color - ; - RParms struc - dw ? ;pushed BP - dw ? ;return address - ReadX dw ? ;X coordinate from which to read - ReadY dw ? ;Y coordinate from which to read - RParms ends - ; - public _Read360x480Dot - _Read360x480Dotprocnear - push bp ;preserve caller's BP - mov bp,sp ;point to stack frame - push si ;preserve C register vars - push di - mov ax,VGA_SEGMENT - mov es,ax ;point to display memory - mov ax,SCREEN_WIDTH/4 ;there are 4 pixels at each address, so - ; each 360-pixel row is 90 bytes wide - ; in each plane + mov al,11h ; cr11 + out dx,al ; current value + inc dx ; point to data + in al,dx ; get cr11 value + and al,7fh ; remove cr0 -> cr7 + out dx,al ; write protect + dec dx ; point to index + cld + mov si,offset vptbl + mov cx,((offset vpend)-(offset vptbl)) shr 1 +@b: lodsw + out dx,ax + loop @b + pop di ;restore C register vars + pop si + ret +_Set360x480Modeendp +; +; Draws a pixel in the specified color at the specified +; location in 360x480 256-color mode. +; +; Call as: void Draw360x480Dot(int X, int Y, int Color) +; +; Returns: nothing +; +DParms struc + dw ? ;pushed BP + dw ? ;return address +DrawX dw ? ;X coordinate at which to draw +DrawY dw ? ;Y coordinate at which to draw +Color dw ? ;color in which to draw (in the + ; range 0-255; upper byte ignored) +DParms ends +; + public _Draw360x480Dot +_Draw360x480Dotprocnear + push bp ;preserve caller's BP + mov bp,sp ;point to stack frame + push si ;preserve C register vars + push di + mov ax,VGA_SEGMENT + mov es,ax ;point to display memory + mov ax,SCREEN_WIDTH/4 + ;there are 4 pixels at each address, so + ; each 360-pixel row is 90 bytes wide + ; in each plane + mul [bp+DrawY] ;point to start of desired row + 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 + mov cl,byte ptr [bp+DrawX] ;get the X coordinate again + and cl,3 ;get the plane # of the pixel + mov ah,1 + shl ah,cl ;set the bit corresponding to the plane + ; the pixel is in + mov al,MAP_MASK + mov dx,SC_INDEX + OUT_WORD ;set to write to the proper plane for + ; the pixel + mov al,byte ptr [bp+Color] ;get the color + stosb ;draw the pixel + pop di ;restore C register vars + pop si + pop bp ;restore caller's BP + ret +_Draw360x480Dotendp +; +; Reads the color of the pixel at the specified +; location in 360x480 256-color mode. +; +; Call as: int Read360x480Dot(int X, int Y) +; +; Returns: pixel color +; +RParms struc + dw ? ;pushed BP + dw ? ;return address +ReadX dw ? ;X coordinate from which to read +ReadY dw ? ;Y coordinate from which to read +RParms ends +; + public _Read360x480Dot +_Read360x480Dotprocnear + push bp ;preserve caller's BP + mov bp,sp ;point to stack frame + push si ;preserve C register vars + push di + mov ax,VGA_SEGMENT + mov es,ax ;point to display memory + mov ax,SCREEN_WIDTH/4 ;there are 4 pixels at each address, so + ; each 360-pixel row is 90 bytes wide + ; in each plane - mul [bp+DrawY] ;point to start of desired row - 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 - mov ah,byte ptr [bp+DrawX] ;get the X coordinate again - and ah,3 - ;get the plane # of the pixel - mov al,READ_MAP - mov dx,GC_INDEX - OUT_WORD ;set to read from the proper plane for - ; the pixel - lods byte ptr es:[si] ;read the pixel - 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 - ret - _Read360x480Dot endp - _TEX Tends - end + mul [bp+DrawY] ;point to start of desired row + 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 + mov ah,byte ptr [bp+DrawX] ;get the X coordinate again + and ah,3 + ;get the plane # of the pixel + mov al,READ_MAP + mov dx,GC_INDEX + OUT_WORD ;set to read from the proper plane for + ; the pixel + lods byte ptr es:[si] ;read the pixel + 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 + ret +_Read360x480Dot endp +_TEX Tends + end +``` diff --git a/32-03.md b/32-03.md index 53baff3..94d8cd9 100644 --- a/32-03.md +++ b/32-03.md @@ -12,206 +12,209 @@ pages: 615-618 **LISTING 32.2 L32-2.C** - * Sample program to illustrate VGA line drawing in 360x480 - * 256-color mode. - * - * Compiled with Borland C/C++. - * - * Must be linked with Listing 32.1 with a command line like: - * - * bcc l10-2.c l10-1.asm - * - * By Michael Abrash - */ - #include /* contains geninterrupt */ +```c +/* + * Sample program to illustrate VGA line drawing in 360x480 + * 256-color mode. + * + * Compiled with Borland C/C++. + * + * Must be linked with Listing 32.1 with a command line like: + * + * bcc l10-2.c l10-1.asm + * + * By Michael Abrash + */ +#include /* contains geninterrupt */ - #define TEXT_MODE 0x03 - #define BIOS_VIDEO_INT 0x10 - #define X_MAX 360 /* working screen width */ - #define Y_MAX 480 /* working screen height */ +#define TEXT_MODE 0x03 +#define BIOS_VIDEO_INT 0x10 +#define X_MAX 360 /* working screen width */ +#define Y_MAX 480 /* working screen height */ - extern void Draw360x480Dot(); - extern void Set360x480Mode(); +extern void Draw360x480Dot(); +extern void Set360x480Mode(); - /* - * Draws a line in octant 0 or 3 ( |DeltaX| >= DeltaY ). - * |DeltaX|+1 points are drawn. - */ - void Octant0(X0, Y0, DeltaX, DeltaY, XDirection, Color) - unsigned int X0, Y0; /* coordinates of start of the line */ - unsigned int DeltaX, DeltaY; /* length of the line */ - int XDirection; /* 1 if line is drawn left to right, - -1 if drawn right to left */ - int Color; /* color in which to draw line */ - { - int DeltaYx2; - int DeltaYx2MinusDeltaXx2; - int ErrorTerm; +/* + * Draws a line in octant 0 or 3 ( |DeltaX| >= DeltaY ). + * |DeltaX|+1 points are drawn. + */ +void Octant0(X0, Y0, DeltaX, DeltaY, XDirection, Color) +unsigned int X0, Y0; /* coordinates of start of the line */ +unsigned int DeltaX, DeltaY; /* length of the line */ +int XDirection; /* 1 if line is drawn left to right, + -1 if drawn right to left */ +int Color; /* color in which to draw line */ +{ + int DeltaYx2; + int DeltaYx2MinusDeltaXx2; + int ErrorTerm; - /* Set up initial error term and values used inside drawing loop */ - DeltaYx2 = DeltaY * 2; - DeltaYx2MinusDeltaXx2 = DeltaYx2 - (int) ( DeltaX * 2 ); - ErrorTerm = DeltaYx2 - (int) DeltaX; + /* Set up initial error term and values used inside drawing loop */ + DeltaYx2 = DeltaY * 2; + DeltaYx2MinusDeltaXx2 = DeltaYx2 - (int) ( DeltaX * 2 ); + ErrorTerm = DeltaYx2 - (int) DeltaX; - /* Draw the line */ - Draw360x480Dot(X0, Y0, Color); /* draw the first pixel */ - while ( DeltaX-- ) { - /* See if it's time to advance the Y coordinate */ - if ( ErrorTerm >= 0 ) { - /* Advance the Y coordinate & adjust the error term - back down */ - Y0++; - ErrorTerm += DeltaYx2MinusDeltaXx2; - } else { - /* Add to the error term */ - ErrorTerm += DeltaYx2; - } - X0 += XDirection; /* advance the X coordinate */ - Draw360x480Dot(X0, Y0, Color); /* draw a pixel */ - } - } + /* Draw the line */ + Draw360x480Dot(X0, Y0, Color); /* draw the first pixel */ + while ( DeltaX-- ) { + /* See if it's time to advance the Y coordinate */ + if ( ErrorTerm >= 0 ) { + /* Advance the Y coordinate & adjust the error term + back down */ + Y0++; + ErrorTerm += DeltaYx2MinusDeltaXx2; + } else { + /* Add to the error term */ + ErrorTerm += DeltaYx2; + } + X0 += XDirection; /* advance the X coordinate */ + Draw360x480Dot(X0, Y0, Color); /* draw a pixel */ + } +} - /* - * Draws a line in octant 1 or 2 ( |DeltaX| < DeltaY ). - * |DeltaY|+1 points are drawn. - */ - void Octant1(X0, Y0, DeltaX, DeltaY, XDirection, Color) - unsigned int X0, Y0; /* coordinates of start of the line */ - unsigned int DeltaX, DeltaY; /* length of the line */ - int XDirection; /* 1 if line is drawn left to right, - -1 if drawn right to left */ - int Color; /* color in which to draw line */ - { - int DeltaXx2; - int DeltaXx2MinusDeltaYx2; - int ErrorTerm; +/* + * Draws a line in octant 1 or 2 ( |DeltaX| < DeltaY ). + * |DeltaY|+1 points are drawn. + */ +void Octant1(X0, Y0, DeltaX, DeltaY, XDirection, Color) +unsigned int X0, Y0; /* coordinates of start of the line */ +unsigned int DeltaX, DeltaY; /* length of the line */ +int XDirection; /* 1 if line is drawn left to right, + -1 if drawn right to left */ +int Color; /* color in which to draw line */ +{ + int DeltaXx2; + int DeltaXx2MinusDeltaYx2; + int ErrorTerm; - /* Set up initial error term and values used inside drawing loop */ - DeltaXx2 = DeltaX * 2; - DeltaXx2MinusDeltaYx2 = DeltaXx2 - (int) ( DeltaY * 2 ); - ErrorTerm = DeltaXx2 - (int) DeltaY; + /* Set up initial error term and values used inside drawing loop */ + DeltaXx2 = DeltaX * 2; + DeltaXx2MinusDeltaYx2 = DeltaXx2 - (int) ( DeltaY * 2 ); + ErrorTerm = DeltaXx2 - (int) DeltaY; - Draw360x480Dot(X0, Y0, Color);/* draw the first pixel */ - while ( DeltaY-- ) { - /* See if it's time to advance the X coordinate */ - if ( ErrorTerm >= 0 ) { - /* Advance the X coordinate & adjust the error term - back down */ - X0 += XDirection; - ErrorTerm += DeltaXx2MinusDeltaYx2; - } else { - /* Add to the error term */ - ErrorTerm += DeltaXx2; - } - Y0++; /* advance the Y coordinate */ - Draw360x480Dot(X0, Y0,Color); /* draw a pixel */ - } - } + Draw360x480Dot(X0, Y0, Color);/* draw the first pixel */ + while ( DeltaY-- ) { + /* See if it's time to advance the X coordinate */ + if ( ErrorTerm >= 0 ) { + /* Advance the X coordinate & adjust the error term + back down */ + X0 += XDirection; + ErrorTerm += DeltaXx2MinusDeltaYx2; + } else { + /* Add to the error term */ + ErrorTerm += DeltaXx2; + } + Y0++; /* advance the Y coordinate */ + Draw360x480Dot(X0, Y0,Color); /* draw a pixel */ + } +} - /* - * Draws a line on the EGA or VGA. - */ - void EVGALine(X0, Y0, X1, Y1, Color) - int X0, Y0; /* coordinates of one end of the line */ - int X1, Y1; /* coordinates of the other end of the line */ - unsigned char Color; /* color in which to draw line */ - { - int DeltaX, DeltaY; - int Temp; +/* + * Draws a line on the EGA or VGA. + */ +void EVGALine(X0, Y0, X1, Y1, Color) +int X0, Y0; /* coordinates of one end of the line */ +int X1, Y1; /* coordinates of the other end of the line */ +unsigned char Color; /* color in which to draw line */ +{ + int DeltaX, DeltaY; + int Temp; - /* Save half the line-drawing cases by swapping Y0 with Y1 - and X0 with X1 if Y0 is greater than Y1. As a result, DeltaY - is always > 0, and only the octant 0-3 cases need to be - handled. */ - if ( Y0 > Y1 ) { - Temp = Y0; - Y0 = Y1; - Y1 = Temp; - Temp = X0; - X0 = X1; - X1 = Temp; - } + /* Save half the line-drawing cases by swapping Y0 with Y1 + and X0 with X1 if Y0 is greater than Y1. As a result, DeltaY + is always > 0, and only the octant 0-3 cases need to be + handled. */ + if ( Y0 > Y1 ) { + Temp = Y0; + Y0 = Y1; + Y1 = Temp; + Temp = X0; + X0 = X1; + X1 = Temp; + } - /* Handle as four separate cases, for the four octants in which - Y1 is greater than Y0 */ - DeltaX = X1 - X0; /* calculate the length of the line - in each coordinate */ - DeltaY = Y1 - Y0; - if ( DeltaX > 0 ) { - if ( DeltaX > DeltaY ) { - Octant0(X0, Y0, DeltaX, DeltaY, 1, Color); - } else { - Octant1(X0, Y0, DeltaX, DeltaY, 1, Color); - } - } else { - DeltaX = -DeltaX; /* absolute value of DeltaX */ - if ( DeltaX > DeltaY ) { - Octant0(X0, Y0, DeltaX, DeltaY, -1, Color); - } else { - Octant1(X0, Y0, DeltaX, DeltaY, -1, Color); - } - } - } + /* Handle as four separate cases, for the four octants in which + Y1 is greater than Y0 */ + DeltaX = X1 - X0; /* calculate the length of the line + in each coordinate */ + DeltaY = Y1 - Y0; + if ( DeltaX > 0 ) { + if ( DeltaX > DeltaY ) { + Octant0(X0, Y0, DeltaX, DeltaY, 1, Color); + } else { + Octant1(X0, Y0, DeltaX, DeltaY, 1, Color); + } + } else { + DeltaX = -DeltaX; /* absolute value of DeltaX */ + if ( DeltaX > DeltaY ) { + Octant0(X0, Y0, DeltaX, DeltaY, -1, Color); + } else { + Octant1(X0, Y0, DeltaX, DeltaY, -1, Color); + } + } +} - /* - * Subroutine to draw a rectangle full of vectors, of the - * specified length and in varying colors, around the - * specified rectangle center. - */ - void VectorsUp(XCenter, YCenter, XLength, YLength) - int XCenter, YCenter; /* center of rectangle to fill */ - int XLength, YLength; /* distance from center to edge - of rectangle */ - { - int WorkingX, WorkingY, Color = 1; +/* + * Subroutine to draw a rectangle full of vectors, of the + * specified length and in varying colors, around the + * specified rectangle center. + */ +void VectorsUp(XCenter, YCenter, XLength, YLength) +int XCenter, YCenter; /* center of rectangle to fill */ +int XLength, YLength; /* distance from center to edge + of rectangle */ +{ + int WorkingX, WorkingY, Color = 1; - /* Lines from center to top of rectangle */ - WorkingX = XCenter - XLength; - WorkingY = YCenter - YLength; - for ( ; WorkingX < ( XCenter + XLength ); WorkingX++ ) - EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color++); + /* Lines from center to top of rectangle */ + WorkingX = XCenter - XLength; + WorkingY = YCenter - YLength; + for ( ; WorkingX < ( XCenter + XLength ); WorkingX++ ) + EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color++); - /* Lines from center to right of rectangle */ - WorkingX = XCenter + XLength - 1; - WorkingY = YCenter - YLength; - for ( ; WorkingY < ( YCenter + YLength ); WorkingY++ ) - EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color++); + /* Lines from center to right of rectangle */ + WorkingX = XCenter + XLength - 1; + WorkingY = YCenter - YLength; + for ( ; WorkingY < ( YCenter + YLength ); WorkingY++ ) + EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color++); - /* Lines from center to bottom of rectangle */ - WorkingX = XCenter + XLength - 1; - WorkingY = YCenter + YLength - 1; - for ( ; WorkingX >= ( XCenter - XLength ); WorkingX-- ) - EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color++); + /* Lines from center to bottom of rectangle */ + WorkingX = XCenter + XLength - 1; + WorkingY = YCenter + YLength - 1; + for ( ; WorkingX >= ( XCenter - XLength ); WorkingX-- ) + EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color++); - /* Lines from center to left of rectangle */ - WorkingX = XCenter - XLength; - WorkingY = YCenter + YLength - 1; - for ( ; WorkingY >= ( YCenter - YLength ); WorkingY-- ) - EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color++); - } + /* Lines from center to left of rectangle */ + WorkingX = XCenter - XLength; + WorkingY = YCenter + YLength - 1; + for ( ; WorkingY >= ( YCenter - YLength ); WorkingY-- ) + EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color++); +} - /* - * Sample program to draw four rectangles full of lines. - */ - void main() - { - char temp; +/* + * Sample program to draw four rectangles full of lines. + */ +void main() +{ + char temp; - Set360x480Mode(); + Set360x480Mode(); - /* Draw each of four rectangles full of vectors */ - VectorsUp(X_MAX / 4, Y_MAX / 4, X_MAX / 4, Y_MAX / 4, 1); - VectorsUp(X_MAX * 3 / 4, Y_MAX / 4, X_MAX / 4, Y_MAX / 4, 2); - VectorsUp(X_MAX / 4, Y_MAX * 3 / 4, X_MAX / 4, Y_MAX / 4, 3); - VectorsUp(X_MAX * 3 / 4, Y_MAX * 3 / 4, X_MAX / 4, Y_MAX / 4, 4); + /* Draw each of four rectangles full of vectors */ + VectorsUp(X_MAX / 4, Y_MAX / 4, X_MAX / 4, Y_MAX / 4, 1); + VectorsUp(X_MAX * 3 / 4, Y_MAX / 4, X_MAX / 4, Y_MAX / 4, 2); + VectorsUp(X_MAX / 4, Y_MAX * 3 / 4, X_MAX / 4, Y_MAX / 4, 3); + 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); + /* Wait for the enter key to be pressed */ + scanf("%c", &temp); - /* Back to text mode */ - _AX = TEXT_MODE; - geninterrupt(BIOS_VIDEO_INT); - } + /* 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 of 360x480 256-color mode is pretty good, especially considering that diff --git a/33-04.md b/33-04.md index bb1ac61..782f3a5 100644 --- a/33-04.md +++ b/33-04.md @@ -12,181 +12,183 @@ pages: 632-636 **LISTING 33.1 L33-1.ASM** - ; Program to demonstrate use of the DAC registers by selecting a - ; smoothly contiguous set of 256 colors, then filling the screen - ; with concentric diamonds in all 256 colors so that they blend - ; into one another to form a continuum of color. - ; - .model small - .stack 200h - .data +```nasm +; Program to demonstrate use of the DAC registers by selecting a +; smoothly contiguous set of 256 colors, then filling the screen +; with concentric diamonds in all 256 colors so that they blend +; into one another to form a continuum of color. +; + .model small + .stack 200h + .data - ; Table used to set all 256 DAC entries. - ; - ; Table format: - ; Byte 0: DAC register 0 red value - ; Byte 1: DAC register 0 green value - ; Byte 2: DAC register 0 blue value - ; Byte 3: DAC register 1 red value - ; Byte 4: DAC register 1 green value - ; Byte 5: DAC register 1 blue value - ; : - ; Byte 765: DAC register 255 red value - ; Byte 766: DAC register 255 green value - ; Byte 767: DAC register 255 blue value +; Table used to set all 256 DAC entries. +; +; Table format: +; Byte 0: DAC register 0 red value +; Byte 1: DAC register 0 green value +; Byte 2: DAC register 0 blue value +; Byte 3: DAC register 1 red value +; Byte 4: DAC register 1 green value +; Byte 5: DAC register 1 blue value +; : +; Byte 765: DAC register 255 red value +; Byte 766: DAC register 255 green value +; Byte 767: DAC register 255 blue value - ColorTable label byte +ColorTable label byte - ; The first 64 entries are increasingly dim pure green. - X=0 - REPT 64 - db 0,63-X,0 - X=X+1 - ENDM +; The first 64 entries are increasingly dim pure green. +X=0 + REPT 64 + db 0,63-X,0 +X=X+1 + ENDM - ; The next 64 entries are increasingly strong pure blue. - X=0 - REPT 64 - db 0,0,X - X=X+1 - ENDM +; The next 64 entries are increasingly strong pure blue. +X=0 + REPT 64 + db 0,0,X +X=X+1 + ENDM - ; The next 64 entries fade through violet to red. - X=0 - REPT 64 - db X,0,63-X - X=X+1 - ENDM +; The next 64 entries fade through violet to red. +X=0 + REPT 64 + db X,0,63-X +X=X+1 + ENDM - ; The last 64 entries are increasingly dim pure red. - X=0 - REPT 64 - db 63-X,0,0 - X=X+1 - ENDM +; The last 64 entries are increasingly dim pure red. +X=0 + REPT 64 + db 63-X,0,0 +X=X+1 +ENDM - .code - Start: - mov ax,0013h ;AH=0 selects set mode function, - ; AL=13h selects 320x200 256-color - int 10h ; mode + .code +Start: + mov ax,0013h ;AH=0 selects set mode function, + ; AL=13h selects 320x200 256-color + int 10h ; mode - ;load the DAC registers with the - ; color settings - mov ax,@data ;point ES to the default - mov es,ax ; data segment - mov dx,offset ColorTable - ;point ES:DX to the start of the - ; block of RGB three-byte values - ; to load into the DAC registers - mov ax,1012h ;AH=10h selects set color function, - ; AL=12h selects set block of DAC - ; registers subfunction - sub bx,bx ;load the block of registers - ; starting at DAC register #0 - mov cx,100h ;set all 256 registers - int 10h ;load the DAC registers + ;load the DAC registers with the + ; color settings + mov ax,@data ;point ES to the default + mov es,ax ; data segment + mov dx,offset ColorTable + ;point ES:DX to the start of the + ; block of RGB three-byte values + ; to load into the DAC registers + mov ax,1012h ;AH=10h selects set color function, + ; AL=12h selects set block of DAC + ; registers subfunction + sub bx,bx ;load the block of registers + ; starting at DAC register #0 + mov cx,100h ;set all 256 registers + int 10h ;load the DAC registers - ;now fill the screen with - ; concentric diamonds in all 256 - ; color attributes - mov ax,0a000h ;point DS to the display memory - mov ds,ax ; segment - ; - ;draw diagonal lines in the upper- - ; left quarter of the screen - mov al,2 ;start with color attribute #2 - mov ah,-1 ;cycle down through the colors - mov bx,320 ;draw top to bottom (distance from - ; one line to the next) - mov dx,160 ;width of rectangle - mov si,100 ;height of rectangle - sub di,di ;start at (0,0) - mov bp,1 ;draw left to right (distance from - ; one column to the next) - call FillBlock ;draw it - ; - ;draw diagonal lines in the upper- - ; right quarter of the screen - mov al,2 ;start with color attribute #2 - mov ah,-1 ;cycle down through the colors - mov bx,320 ;draw top to bottom (distance from - ; one line to the next) - mov dx,160 ;width of rectangle - mov si,100 ;height of rectangle - mov di,319 ;start at (319,0) - mov bp,-1 ;draw right to left (distance from - ; one column to the next) - callFillBlock;draw it + ;now fill the screen with + ; concentric diamonds in all 256 + ; color attributes + mov ax,0a000h ;point DS to the display memory + mov ds,ax ; segment + ; + ;draw diagonal lines in the upper- + ; left quarter of the screen + mov al,2 ;start with color attribute #2 + mov ah,-1 ;cycle down through the colors + mov bx,320 ;draw top to bottom (distance from + ; one line to the next) + mov dx,160 ;width of rectangle + mov si,100 ;height of rectangle + sub di,di ;start at (0,0) + mov bp,1 ;draw left to right (distance from + ; one column to the next) + call FillBlock ;draw it + ; + ;draw diagonal lines in the upper- + ; right quarter of the screen + mov al,2 ;start with color attribute #2 + mov ah,-1 ;cycle down through the colors + mov bx,320 ;draw top to bottom (distance from + ; one line to the next) + mov dx,160 ;width of rectangle + mov si,100 ;height of rectangle + mov di,319 ;start at (319,0) + mov bp,-1 ;draw right to left (distance from + ; one column to the next) +callFillBlock;draw it - ;draw diagonal lines in the lower- - ; left quarter of the screen - mov al,2 ;start with color attribute #2 - mov ah,-1 ;cycle down through the colors - mov bx,-320 ;draw bottom to top (distance from - ; one line to the next) - mov dx,160 ;width of rectangle - mov si,100 ;height of rectangle - mov di,199*320 ;start at (0,199) - mov bp,1 ;draw left to right (distance from - ; one column to the next) - callFillBlock;draw it - ; - ;draw diagonal lines in the lower- - ; right quarter of the screen - mov al,2 ;start with color attribute #2 - mov ah,-1 ;cycle down through the colors - mov bx,-320 ;draw bottom to top (distance from - ; one line to the next) - mov dx,160 ;width of rectangle - mov si,100 ;height of rectangle - mov di,199*320+319 ;start at (319,199) - mov bp,-1 ;draw right to left (distance from - ; one column to the next) - callFillBlock;draw it + ;draw diagonal lines in the lower- + ; left quarter of the screen + mov al,2 ;start with color attribute #2 + mov ah,-1 ;cycle down through the colors + mov bx,-320 ;draw bottom to top (distance from + ; one line to the next) + mov dx,160 ;width of rectangle + mov si,100 ;height of rectangle + mov di,199*320 ;start at (0,199) + mov bp,1 ;draw left to right (distance from + ; one column to the next) +callFillBlock;draw it + ; + ;draw diagonal lines in the lower- + ; right quarter of the screen + mov al,2 ;start with color attribute #2 + mov ah,-1 ;cycle down through the colors + mov bx,-320 ;draw bottom to top (distance from + ; one line to the next) + mov dx,160 ;width of rectangle + mov si,100 ;height of rectangle + mov di,199*320+319 ;start at (319,199) + mov bp,-1 ;draw right to left (distance from + ; one column to the next) +callFillBlock;draw it - mov ah,1 ;wait for a key - int21h; + mov ah,1 ;wait for a key +int21h; - mov ax,0003h ;return to text mode - int10h; + mov ax,0003h ;return to text mode +int10h; - mov ah,4ch ;done--return to DOS - int21h + mov ah,4ch ;done--return to DOS +int21h - ; Fills the specified rectangular area of the screen with diagonal lines. - ; - ; Input: - ; AL = initial attribute with which to draw - ; AH = amount by which to advance the attribute from - ; one pixel to the next - ; BX = distance to advance from one pixel to the next - ; DX = width of rectangle to fill - ; SI = height of rectangle to fill - ; DS:DN = screen address of first pixel to draw - ; BP = offset from the start of one column to the start of - ; the next +; Fills the specified rectangular area of the screen with diagonal lines. +; +; Input: +; AL = initial attribute with which to draw +; AH = amount by which to advance the attribute from +; one pixel to the next +; BX = distance to advance from one pixel to the next +; DX = width of rectangle to fill +; SI = height of rectangle to fill +; DS:DN = screen address of first pixel to draw +; BP = offset from the start of one column to the start of +; the next - FillBlock: - FillHorzLoop: - push di ;preserve pointer to top of column - push ax ;preserve initial attribute - mov cx,si ;column height - FillVertLoop: - mov [di],al ;set the pixel - add di,bx ;point to the next row in the column - add al,ah ;advance the attribute - loop FillVertLoop ; - pop ax ;restore initial attribute - add al,ah ;advance to the next attribute to - ; start the next column - pop di ;retrieve pointer to top of column - add di,bp ;point to next column - dec dx ;have we done all columns? - jnz FillHorzLoop ;no, do the next column - ret; +FillBlock: +FillHorzLoop: + push di ;preserve pointer to top of column + push ax ;preserve initial attribute + mov cx,si ;column height +FillVertLoop: + mov [di],al ;set the pixel + add di,bx ;point to the next row in the column + add al,ah ;advance the attribute + loop FillVertLoop ; + pop ax ;restore initial attribute + add al,ah ;advance to the next attribute to + ; start the next column + pop di ;retrieve pointer to top of column + add di,bp ;point to next column + dec dx ;have we done all columns? + jnz FillHorzLoop ;no, do the next column + ret; - endStart + endStart +``` Note the jagged lines at the corners of the screen when you run Listing 33.1. This shows how coarse the 320x200 resolution of mode 13H actually diff --git a/34-03.md b/34-03.md index 997d55e..77cc1c0 100644 --- a/34-03.md +++ b/34-03.md @@ -32,252 +32,254 @@ in.) **LISTING 34.1 L34-1.ASM** - ; Fills a band across the screen with vertical bars in all 256 - ; attributes, then cycles a portion of the palette until a key is - ; pressed. - ; Assemble with MASM or TASM +```nasm +; Fills a band across the screen with vertical bars in all 256 +; attributes, then cycles a portion of the palette until a key is +; pressed. +; Assemble with MASM or TASM - USE_BIOS equ 1 ;set to 1 to use BIOS functions to access the - ; DAC, 0 to read and write the DAC directly - GUARD_AGAINST_INTS equ 1 ;1 to turn off interrupts and set write index - ; before loading each DAC location, 0 to rely - ; on the DAC auto-incrementing - WAIT_VSYNC equ 1 ;set to 1 to wait for the leading edge of - ; vertical sync before accessing the DAC, 0 - ; not to wait - NOT_8088 equ 0 ;set to 1 to use REP INSB and REP OUTSB when - ; accessing the DAC directly, 0 to use - ; IN/STOSB and LODSB/OUT - CYCLE_SIZE equ 256 ;# of DAC locations to cycle, 256 max - SCREEN_SEGMENT equ 0a000h ;mode 13h display memory segment - SCREEN_WIDTH_IN_BYTES equ 320 ;# of bytes across the screen in mode 13h - INPUT_STATUS_1 equ 03dah ;input status 1 register port - DAC_READ_INDEX equ 03c7h ;DAC Read Index register - DAC_WRITE_INDEX equ 03c8h ;DAC Write Index register - DAC_DATA equ 03c9h ;DAC Data register +USE_BIOS equ 1 ;set to 1 to use BIOS functions to access the + ; DAC, 0 to read and write the DAC directly +GUARD_AGAINST_INTS equ 1 ;1 to turn off interrupts and set write index + ; before loading each DAC location, 0 to rely + ; on the DAC auto-incrementing +WAIT_VSYNC equ 1 ;set to 1 to wait for the leading edge of + ; vertical sync before accessing the DAC, 0 + ; not to wait +NOT_8088 equ 0 ;set to 1 to use REP INSB and REP OUTSB when + ; accessing the DAC directly, 0 to use + ; IN/STOSB and LODSB/OUT +CYCLE_SIZE equ 256 ;# of DAC locations to cycle, 256 max +SCREEN_SEGMENT equ 0a000h ;mode 13h display memory segment +SCREEN_WIDTH_IN_BYTES equ 320 ;# of bytes across the screen in mode 13h +INPUT_STATUS_1 equ 03dah ;input status 1 register port +DAC_READ_INDEX equ 03c7h ;DAC Read Index register +DAC_WRITE_INDEX equ 03c8h ;DAC Write Index register +DAC_DATA equ 03c9h ;DAC Data register - if NOT_8088 - .286 - endif ;NOT_8088 +if NOT_8088 + .286 +endif ;NOT_8088 - .model small - .stack 100h - .data - ;Storage for all 256 DAC locations, organized as one three-byte - ; (actually three 6-bit values; upper two bits of each byte aren't - ; significant) RGB triplet per color. - PaletteTempdb 256*3 dup(?) - .code - start: - mov ax,@data - mov ds,ax + .model small + .stack 100h + .data +;Storage for all 256 DAC locations, organized as one three-byte +; (actually three 6-bit values; upper two bits of each byte aren't +; significant) RGB triplet per color. +PaletteTempdb 256*3 dup(?) + .code +start: + mov ax,@data + mov ds,ax - ;Select VGA's standard 256-color graphics mode, mode 13h. - mov ax,0013h ;AH = 0: set mode function, - int 10h ; AL = 13h: mode # to set +;Select VGA's standard 256-color graphics mode, mode 13h. + mov ax,0013h ;AH = 0: set mode function, + int 10h ; AL = 13h: mode # to set - ;Read all 256 DAC locations into PaletteTemp (3 6-bit values, one - ; each for red, green, and blue, per DAC location). +;Read all 256 DAC locations into PaletteTemp (3 6-bit values, one +; each for red, green, and blue, per DAC location). - if WAIT_VSYNC - ;Wait for the leading edge of the vertical sync pulse; this ensures - ; that we read the DAC starting during the vertical non-display - ; period. - mov dx,INPUT_STATUS_1 - WaitNotVSync: ;wait to be out of vertical sync - in al,dx - and al,08h - jnz WaitNotVSync - WaitVSync: ;wait until vertical sync begins - in al,dx - and al,08h - jz WaitVSync - endif ;WAIT_VSYNC +if WAIT_VSYNC +;Wait for the leading edge of the vertical sync pulse; this ensures +; that we read the DAC starting during the vertical non-display +; period. + mov dx,INPUT_STATUS_1 +WaitNotVSync: ;wait to be out of vertical sync + in al,dx + and al,08h + jnz WaitNotVSync +WaitVSync: ;wait until vertical sync begins + in al,dx + and al,08h + jz WaitVSync +endif ;WAIT_VSYNC - if USE_BIOS - mov ax,1017h ;AH = 10h: set DAC function, - ; AL = 17h: read DAC block subfunction - sub bx,bx ;start with DAC location 0 - mov cx,256 ;read out all 256 locations - mov dx,seg PaletteTemp - mov es,dx - mov dx,offset PaletteTemp ;point ES:DX to array in which - ; the DAC values are to be stored - int 10h ;read the DAC - else ;!USE_BIOS - if GUARD_AGAINST_INTS - mov cx,CYCLE_SIZE ;# of DAC locations to load - mov di,seg PaletteTemp - mov es,di - mov di,offset PaletteTemp ;dump the DAC into this array - sub ah,ah ;start with DAC location 0 - DACStoreLoop: - mov dx,DAC_READ_INDEX - mov al,ah - cli - out dx,al ;set the DAC location # - mov dx,DAC_DATA - in al,dx ;get the red component - stosb - in al,dx ;get the green component - stosb - in al,dx ;get the blue component - stosb - sti - inc ah - loopDACStoreLoop - else;!GUARD_AGAINST_INTS - mov dx,DAC_READ_INDEX - sub al,al - out dx,al ;set the initial DAC location to 0 - mov di,seg PaletteTemp - mov es,di - mov di,offset PaletteTemp ;dump the DAC into this array - mov dx,DAC_DATA - if NOT_8088 - mov cx,CYCLE_SIZE*3 - rep insb ;read CYCLE_SIZE DAC locations at once - else;!NOT_8088 - mov cx,CYCLE_SIZE ;# of DAC locations to load - DACStoreLoop: - in al,dx ;get the red component - stosb - in al,dx ;get the green component - stosb - in al,dx ;get the blue component - stosb - loop DACStoreLoop - endif ;NOT_8088 - endif ;GUARD_AGAINST_INTS - endif ;USE_BIOS +if USE_BIOS + mov ax,1017h ;AH = 10h: set DAC function, + ; AL = 17h: read DAC block subfunction + sub bx,bx ;start with DAC location 0 + mov cx,256 ;read out all 256 locations + mov dx,seg PaletteTemp + mov es,dx + mov dx,offset PaletteTemp ;point ES:DX to array in which + ; the DAC values are to be stored + int 10h ;read the DAC +else ;!USE_BIOS + if GUARD_AGAINST_INTS + mov cx,CYCLE_SIZE ;# of DAC locations to load + mov di,seg PaletteTemp + mov es,di + mov di,offset PaletteTemp ;dump the DAC into this array + sub ah,ah ;start with DAC location 0 +DACStoreLoop: + mov dx,DAC_READ_INDEX + mov al,ah + cli + out dx,al ;set the DAC location # + mov dx,DAC_DATA + in al,dx ;get the red component + stosb + in al,dx ;get the green component + stosb + in al,dx ;get the blue component + stosb + sti + inc ah + loopDACStoreLoop + else;!GUARD_AGAINST_INTS + mov dx,DAC_READ_INDEX + sub al,al + out dx,al ;set the initial DAC location to 0 + mov di,seg PaletteTemp + mov es,di + mov di,offset PaletteTemp ;dump the DAC into this array + mov dx,DAC_DATA + if NOT_8088 + mov cx,CYCLE_SIZE*3 + rep insb ;read CYCLE_SIZE DAC locations at once + else;!NOT_8088 + mov cx,CYCLE_SIZE ;# of DAC locations to load +DACStoreLoop: + in al,dx ;get the red component + stosb + in al,dx ;get the green component + stosb + in al,dx ;get the blue component + stosb + loop DACStoreLoop + endif ;NOT_8088 + endif ;GUARD_AGAINST_INTS +endif ;USE_BIOS - ;Draw a series of 1-pixel-wide vertical bars across the screen in - ; attributes 1 through 255. - mov ax,SCREEN_SEGMENT - mov es,ax - mov di,50*SCREEN_WIDTH_IN_BYTES ;point ES:DI to the start - ; of line 50 on the screen - cld - mov dx,100 ;draw 100 lines high - RowLoop: - mov al,1 ;start each line with attr 1 - mov cx,SCREEN_WIDTH_IN_BYTES ;do a full line across - ColumnLoop: - stosb ;draw a pixel - add al,1 ;increment the attribute - ad cal,0 ;if the attribute just turned - ; over to 0, increment it to 1 - ; because we're not going to - ; cycle DAC location 0, so - ; attribute 0 won't change - loop ColumnLoop - dec dx - jnz RowLoop +;Draw a series of 1-pixel-wide vertical bars across the screen in +; attributes 1 through 255. + mov ax,SCREEN_SEGMENT + mov es,ax + mov di,50*SCREEN_WIDTH_IN_BYTES ;point ES:DI to the start + ; of line 50 on the screen + cld + mov dx,100 ;draw 100 lines high +RowLoop: + mov al,1 ;start each line with attr 1 + mov cx,SCREEN_WIDTH_IN_BYTES ;do a full line across +ColumnLoop: + stosb ;draw a pixel + add al,1 ;increment the attribute + ad cal,0 ;if the attribute just turned + ; over to 0, increment it to 1 + ; because we're not going to + ; cycle DAC location 0, so + ; attribute 0 won't change + loop ColumnLoop + dec dx + jnz RowLoop - ;Cycle the specified range of DAC locations until a key is pressed. - CycleLoop: - ;Rotate colors 1-255 one position in the PaletteTemp array; - ; location 0 is always left unchanged so that the background - ; and border don't change. - push word ptr PaletteTemp+(1*3) ;set aside PaletteTemp - push word ptr PaletteTemp+(1*3)+2; setting for attr 1 - mov cx,254 - mov si,offset PaletteTemp+(2*3) - mov di,offset PaletteTemp+(1*3) - mov ax,ds - mov es,ax - mov cx,254*3/2 - rep movsw ;rotate PaletteTemp settings - ; for attrs 2 through 255 to - ; attrs 1 through 254 - pop bx ;get back original settings - pop ax ; for attribute 1 and move - stosw ; them to the PaletteTemp - mov es:[di],bl ; location for attribute 255 +;Cycle the specified range of DAC locations until a key is pressed. +CycleLoop: +;Rotate colors 1-255 one position in the PaletteTemp array; +; location 0 is always left unchanged so that the background +; and border don't change. + push word ptr PaletteTemp+(1*3) ;set aside PaletteTemp + push word ptr PaletteTemp+(1*3)+2; setting for attr 1 + mov cx,254 + mov si,offset PaletteTemp+(2*3) + mov di,offset PaletteTemp+(1*3) + mov ax,ds + mov es,ax + mov cx,254*3/2 + rep movsw ;rotate PaletteTemp settings + ; for attrs 2 through 255 to + ; attrs 1 through 254 + pop bx ;get back original settings + pop ax ; for attribute 1 and move + stosw ; them to the PaletteTemp + mov es:[di],bl ; location for attribute 255 - if WAIT_VSYNC - ;Wait for the leading edge of the vertical sync pulse; this ensures - ; that we reload the DAC starting during the vertical non-display - ; period. - mov dx,INPUT_STATUS_1 - WaitNotVSync2: ;wait to be out of vertical sync - in al,dx - and al,08h - jnz WaitNotVSync2 - WaitVSync2: ;wait until vertical sync begins - in al,dx - and al,08h - jz WaitVSync2 - endif ;WAIT_VSYNC +if WAIT_VSYNC +;Wait for the leading edge of the vertical sync pulse; this ensures +; that we reload the DAC starting during the vertical non-display +; period. + mov dx,INPUT_STATUS_1 +WaitNotVSync2: ;wait to be out of vertical sync + in al,dx + and al,08h + jnz WaitNotVSync2 +WaitVSync2: ;wait until vertical sync begins + in al,dx + and al,08h + jz WaitVSync2 +endif ;WAIT_VSYNC - if USE_BIOS - ;Set the new, rotated palette. - mov ax,1012h ;AH = 10h: set DAC function, - ; AL = 12h: set DAC block subfunction - sub bx,bx ;start with DAC location 0 - mov cx,CYCLE_SIZE ;# of DAC locations to set - mov dx,seg PaletteTemp - mov es,dx - mov dx,offset PaletteTemp ;point ES:DX to array from which - ; to load the DAC - int 10h ;load the DAC - else ;!USE_BIOS - if GUARD_AGAINST_INTS - mov cx,CYCLE_SIZE ;# of DAC locations to load - mov si,offset PaletteTemp ;load the DAC from this array - sub ah,ah ;start with DAC location 0 - DACLoadLoop: - mov dx,DAC_WRITE_INDEX - mov al,ah - cli - out dx,al ;set the DAC location # - mov dx,DAC_DATA - lodsb - out dx,al ;set the red component - lodsb - out dx,al ;set the green component - lodsb - out dx,al ;set the blue component - sti - inc ah - loop DACLoadLoop - else;!GUARD_AGAINST_INTS - mov dx,DAC_WRITE_INDEX - sub al,al - out dx,al ;set the initial DAC location to 0 - mov si,offset PaletteTemp ;load the DAC from this array - mov dx,DAC_DATA - if NOT_8088 - mov cx,CYCLE_SIZE*3 - rep outsb ;load CYCLE_SIZE DAC locations at once - else;!NOT_8088 - mov cx,CYCLE_SIZE ;# of DAC locations to load - DACLoadLoop: - lodsb - out dx,al ;set the red component - lodsb - out dx,al ;set the green component - lodsb - out dx,al ;set the blue component - loop DACLoadLoop - endif;NOT_8088 - endif;GUARD_AGAINST_INTS - endif;USE_BIOS +if USE_BIOS +;Set the new, rotated palette. + mov ax,1012h ;AH = 10h: set DAC function, + ; AL = 12h: set DAC block subfunction + sub bx,bx ;start with DAC location 0 + mov cx,CYCLE_SIZE ;# of DAC locations to set + mov dx,seg PaletteTemp + mov es,dx + mov dx,offset PaletteTemp ;point ES:DX to array from which + ; to load the DAC + int 10h ;load the DAC +else ;!USE_BIOS + if GUARD_AGAINST_INTS + mov cx,CYCLE_SIZE ;# of DAC locations to load + mov si,offset PaletteTemp ;load the DAC from this array + sub ah,ah ;start with DAC location 0 +DACLoadLoop: + mov dx,DAC_WRITE_INDEX + mov al,ah + cli + out dx,al ;set the DAC location # + mov dx,DAC_DATA + lodsb + out dx,al ;set the red component + lodsb + out dx,al ;set the green component + lodsb + out dx,al ;set the blue component + sti + inc ah + loop DACLoadLoop + else;!GUARD_AGAINST_INTS + mov dx,DAC_WRITE_INDEX + sub al,al + out dx,al ;set the initial DAC location to 0 + mov si,offset PaletteTemp ;load the DAC from this array + mov dx,DAC_DATA + if NOT_8088 + mov cx,CYCLE_SIZE*3 + rep outsb ;load CYCLE_SIZE DAC locations at once + else;!NOT_8088 + mov cx,CYCLE_SIZE ;# of DAC locations to load +DACLoadLoop: + lodsb + out dx,al ;set the red component + lodsb + out dx,al ;set the green component + lodsb + out dx,al ;set the blue component + loop DACLoadLoop + endif;NOT_8088 + endif;GUARD_AGAINST_INTS +endif;USE_BIOS - ;See if a key has been pressed. - mov ah,0bh ;DOS check standard input status fn - int 21h - and al,al ;is a key pending? - jz CycleLoop ;no, cycle some more +;See if a key has been pressed. + mov ah,0bh ;DOS check standard input status fn + int 21h + and al,al ;is a key pending? + jz CycleLoop ;no, cycle some more - ;Clear the keypress. - mov ah,1 ;DOS keyboard input fn - int 21h +;Clear the keypress. + mov ah,1 ;DOS keyboard input fn + int 21h - ;Restore text mode and done. - mov ax,0003h ;AH = 0: set mode function, - int 10h ; AL = 03h: mode # to set - mov ah,4ch ;DOS terminate process fn - int 21h +;Restore text mode and done. + mov ax,0003h ;AH = 0: set mode function, + int 10h ; AL = 03h: mode # to set + mov ah,4ch ;DOS terminate process fn + int 21h - endstart + endstart +``` diff --git a/35-03.md b/35-03.md index ed10562..af95b66 100644 --- a/35-03.md +++ b/35-03.md @@ -19,193 +19,195 @@ 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 - * for the EGA and VGA. Works in modes 0xE, 0xF, 0x10, and 0x12. - * - * Compiled with Borland C++ - * - * By Michael Abrash - */ +```c +/* + * 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++ + * + * By Michael Abrash + */ - #include /* contains MK_FP macro */ +#include /* contains MK_FP macro */ - #define EVGA_SCREEN_WIDTH_IN_BYTES 80 - /* memory offset from start of - one row to start of next */ - #define EVGA_SCREEN_SEGMENT 0xA000 - /* display memory segment */ - #define GC_INDEX 0x3CE - /* Graphics Controller - Index register port */ - #define GC_DATA 0x3CF - /* Graphics Controller - Data register port */ - #define SET_RESET_INDEX 0 /* indexes of needed */ - #define ENABLE_SET_RESET_INDEX 1 /* Graphics Controller */ - #define BIT_MASK_INDEX 8 /* registers */ +#define EVGA_SCREEN_WIDTH_IN_BYTES 80 + /* memory offset from start of + one row to start of next */ +#define EVGA_SCREEN_SEGMENT 0xA000 + /* display memory segment */ +#define GC_INDEX 0x3CE + /* Graphics Controller + Index register port */ +#define GC_DATA 0x3CF + /* Graphics Controller + Data register port */ +#define SET_RESET_INDEX 0 /* indexes of needed */ +#define ENABLE_SET_RESET_INDEX 1 /* Graphics Controller */ +#define BIT_MASK_INDEX 8 /* registers */ - /* - * Draws a dot at (X0,Y0) in whatever color the EGA/VGA hardware is - * set up for. Leaves the bit mask set to whatever value the - * dot required. - */ - void EVGADot(X0, Y0) - unsigned int X0; /* coordinates at which to draw dot, with */ - unsigned int Y0; /* (0,0) at the upper left of the screen */ - { - unsigned char far *PixelBytePtr; - unsigned char PixelMask; +/* + * Draws a dot at (X0,Y0) in whatever color the EGA/VGA hardware is + * set up for. Leaves the bit mask set to whatever value the + * dot required. + */ +void EVGADot(X0, Y0) +unsigned int X0; /* coordinates at which to draw dot, with */ +unsigned int Y0; /* (0,0) at the upper left of the screen */ +{ + unsigned char far *PixelBytePtr; + unsigned char PixelMask; - /* Calculate the offset in the screen segment of the byte in - which the pixel lies */ - PixelBytePtr = MK_FP(EVGA_SCREEN_SEGMENT, - ( Y0 * EVGA_SCREEN_WIDTH_IN_BYTES ) + ( X0 / 8 )); + /* Calculate the offset in the screen segment of the byte in + which the pixel lies */ + 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 - screen byte */ - PixelMask = 0x80 >> ( X0 & 0x07 ); + /* 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 - only the bit corresponding to the pixel being drawn to - be modified */ - outportb(GC_INDEX, BIT_MASK_INDEX); - outportb(GC_DATA, PixelMask); + /* 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. - 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; - } + /* Draw the pixel. Because of the operation of the set/reset + 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; +} - /* - * Draws a line in octant 0 or 3 ( |DeltaX| >= DeltaY ). - */ - void Octant0(X0, Y0, DeltaX, DeltaY, XDirection) - unsigned int X0, Y0; /* coordinates of start of the line */ - unsigned int DeltaX, DeltaY; /* length of the line (both > 0) */ - int XDirection; /* 1 if line is drawn left to right, - -1 if drawn right to left */ - { - int DeltaYx2; - int DeltaYx2MinusDeltaXx2; - int ErrorTerm; +/* + * Draws a line in octant 0 or 3 ( |DeltaX| >= DeltaY ). + */ +void Octant0(X0, Y0, DeltaX, DeltaY, XDirection) +unsigned int X0, Y0; /* coordinates of start of the line */ +unsigned int DeltaX, DeltaY; /* length of the line (both > 0) */ +int XDirection; /* 1 if line is drawn left to right, + -1 if drawn right to left */ +{ + int DeltaYx2; + int DeltaYx2MinusDeltaXx2; + int ErrorTerm; - /* Set up initial error term and values used inside drawing loop */ - DeltaYx2 = DeltaY * 2; - DeltaYx2MinusDeltaXx2 = DeltaYx2 - (int) ( DeltaX * 2 ); - ErrorTerm = DeltaYx2 - (int) DeltaX; + /* Set up initial error term and values used inside drawing loop */ + DeltaYx2 = DeltaY * 2; + DeltaYx2MinusDeltaXx2 = DeltaYx2 - (int) ( DeltaX * 2 ); + ErrorTerm = DeltaYx2 - (int) DeltaX; - /* Draw the line */ - EVGADot(X0, Y0); /* draw the first pixel */ - while ( DeltaX— ) { - /* See if it's time to advance the Y coordinate */ - if ( ErrorTerm >= 0 ) { - /* Advance the Y coordinate & adjust the error term - back down */ - Y0++; - ErrorTerm += DeltaYx2MinusDeltaXx2; - } else { - /* Add to the error term */ - ErrorTerm += DeltaYx2; - } - X0 += XDirection; /* advance the X coordinate */ - EVGADot(X0, Y0); /* draw a pixel */ - } - } + /* Draw the line */ + EVGADot(X0, Y0); /* draw the first pixel */ + while ( DeltaX— ) { + /* See if it's time to advance the Y coordinate */ + if ( ErrorTerm >= 0 ) { + /* Advance the Y coordinate & adjust the error term + back down */ + Y0++; + ErrorTerm += DeltaYx2MinusDeltaXx2; + } else { + /* Add to the error term */ + ErrorTerm += DeltaYx2; + } + X0 += XDirection; /* advance the X coordinate */ + EVGADot(X0, Y0); /* draw a pixel */ + } +} - /* - * Draws a line in octant 1 or 2 ( |DeltaX| < DeltaY ). - */ - void Octant1(X0, Y0, DeltaX, DeltaY, XDirection) - unsigned int X0, Y0; /* coordinates of start of the line */ - unsigned int DeltaX, DeltaY; /* length of the line (both > 0) */ - int XDirection; /* 1 if line is drawn left to right, - -1 if drawn right to left */ - { - int DeltaXx2; - int DeltaXx2MinusDeltaYx2; - int ErrorTerm; +/* + * Draws a line in octant 1 or 2 ( |DeltaX| < DeltaY ). + */ +void Octant1(X0, Y0, DeltaX, DeltaY, XDirection) +unsigned int X0, Y0; /* coordinates of start of the line */ +unsigned int DeltaX, DeltaY; /* length of the line (both > 0) */ +int XDirection; /* 1 if line is drawn left to right, + -1 if drawn right to left */ +{ + int DeltaXx2; + int DeltaXx2MinusDeltaYx2; + int ErrorTerm; - /* Set up initial error term and values used inside drawing loop */ - DeltaXx2 = DeltaX * 2; - DeltaXx2MinusDeltaYx2 = DeltaXx2 - (int) ( DeltaY * 2 ); - ErrorTerm = DeltaXx2 - (int) DeltaY; + /* Set up initial error term and values used inside drawing loop */ + DeltaXx2 = DeltaX * 2; + DeltaXx2MinusDeltaYx2 = DeltaXx2 - (int) ( DeltaY * 2 ); + ErrorTerm = DeltaXx2 - (int) DeltaY; - EVGADot(X0, Y0); /* draw the first pixel */ - while ( DeltaY— ) { - /* See if it's time to advance the X coordinate */ - if ( ErrorTerm >= 0 ) { - /* Advance the X coordinate & adjust the error term - back down */ - X0 += XDirection; - ErrorTerm += DeltaXx2MinusDeltaYx2; - } else { - /* Add to the error term */ - ErrorTerm += DeltaXx2; - } - Y0++; /* advance the Y coordinate */ - EVGADot(X0, Y0); /* draw a pixel */ - } - } + EVGADot(X0, Y0); /* draw the first pixel */ + while ( DeltaY— ) { + /* See if it's time to advance the X coordinate */ + if ( ErrorTerm >= 0 ) { + /* Advance the X coordinate & adjust the error term + back down */ + X0 += XDirection; + ErrorTerm += DeltaXx2MinusDeltaYx2; + } else { + /* Add to the error term */ + ErrorTerm += DeltaXx2; + } + Y0++; /* advance the Y coordinate */ + EVGADot(X0, Y0); /* draw a pixel */ + } +} - /* - * Draws a line on the EGA or VGA. - */ - void EVGALine(X0, Y0, X1, Y1, Color) - int X0, Y0; /* coordinates of one end of the line */ - int X1, Y1; /* coordinates of the other end of the line */ - char Color; /* color to draw line in */ - { - int DeltaX, DeltaY; - int Temp; +/* + * Draws a line on the EGA or VGA. + */ +void EVGALine(X0, Y0, X1, Y1, Color) +int X0, Y0; /* coordinates of one end of the line */ +int X1, Y1; /* coordinates of the other end of the line */ +char Color; /* color to draw line in */ +{ + int DeltaX, DeltaY; + int Temp; - /* Set the drawing color */ + /* Set the drawing color */ - /* Put the drawing color in the Set/Reset register */ - outportb(GC_INDEX, SET_RESET_INDEX); - outportb(GC_DATA, Color); - /* Cause all planes to be forced to the Set/Reset color */ - outportb(GC_INDEX, ENABLE_SET_RESET_INDEX); - outportb(GC_DATA, 0xF); + /* Put the drawing color in the Set/Reset register */ + outportb(GC_INDEX, SET_RESET_INDEX); + outportb(GC_DATA, Color); + /* Cause all planes to be forced to the Set/Reset color */ + outportb(GC_INDEX, ENABLE_SET_RESET_INDEX); + outportb(GC_DATA, 0xF); - /* Save half the line-drawing cases by swapping Y0 with Y1 - and X0 with X1 if Y0 is greater than Y1. As a result, DeltaY - is always > 0, and only the octant 0-3 cases need to be - handled. */ - if ( Y0 > Y1 ) { - Temp = Y0; - Y0 = Y1; - Y1 = Temp; - Temp = X0; - X0 = X1; - X1 = Temp; - } + /* Save half the line-drawing cases by swapping Y0 with Y1 + and X0 with X1 if Y0 is greater than Y1. As a result, DeltaY + is always > 0, and only the octant 0-3 cases need to be + handled. */ + if ( Y0 > Y1 ) { + Temp = Y0; + Y0 = Y1; + Y1 = Temp; + Temp = X0; + X0 = X1; + X1 = Temp; + } - /* Handle as four separate cases, for the four octants in which - Y1 is greater than Y0 */ - DeltaX = X1 - X0; /* calculate the length of the line - in each coordinate */ - DeltaY = Y1 - Y0; - if ( DeltaX > 0 ) { - if ( DeltaX > DeltaY ) { - Octant0(X0, Y0, DeltaX, DeltaY, 1); - } else { - Octant1(X0, Y0, DeltaX, DeltaY, 1); - } - } else { - DeltaX = -DeltaX; /* absolute value of DeltaX */ - if ( DeltaX > DeltaY ) { - Octant0(X0, Y0, DeltaX, DeltaY, -1); - } else { - Octant1(X0, Y0, DeltaX, DeltaY, -1); - } - } + /* Handle as four separate cases, for the four octants in which + Y1 is greater than Y0 */ + DeltaX = X1 - X0; /* calculate the length of the line + in each coordinate */ + DeltaY = Y1 - Y0; + if ( DeltaX > 0 ) { + if ( DeltaX > DeltaY ) { + Octant0(X0, Y0, DeltaX, DeltaY, 1); + } else { + Octant1(X0, Y0, DeltaX, DeltaY, 1); + } + } else { + DeltaX = -DeltaX; /* absolute value of DeltaX */ + if ( DeltaX > DeltaY ) { + Octant0(X0, Y0, DeltaX, DeltaY, -1); + } else { + Octant1(X0, Y0, DeltaX, DeltaY, -1); + } + } - /* Return the state of the EGA/VGA to normal */ - outportb(GC_INDEX, ENABLE_SET_RESET_INDEX); - outportb(GC_DATA, 0); - outportb(GC_INDEX, BIT_MASK_INDEX); - outportb(GC_DATA, 0xFF); - } + /* Return the state of the EGA/VGA to normal */ + outportb(GC_INDEX, ENABLE_SET_RESET_INDEX); + outportb(GC_DATA, 0); + outportb(GC_INDEX, BIT_MASK_INDEX); + outportb(GC_DATA, 0xFF); +} +``` diff --git a/35-04.md b/35-04.md index 78683cc..6d78720 100644 --- a/35-04.md +++ b/35-04.md @@ -12,89 +12,91 @@ pages: 664-667 **LISTING 35.2 L35-2.C** - /* - * Sample program to illustrate EGA/VGA line drawing routines. - * - * Compiled with Borland C++ - * - * By Michael Abrash - */ +```c +/* + * Sample program to illustrate EGA/VGA line drawing routines. + * + * Compiled with Borland C++ + * + * By Michael Abrash + */ - #include /* contains geninterrupt */ +#include /* contains geninterrupt */ - #define GRAPHICS_MODE 0x10 - #define TEXT_MODE 0x03 - #define BIOS_VIDEO_INT 0x10 - #define X_MAX 640 /* working screen width */ - #define Y_MAX 348 /* working screen height */ +#define GRAPHICS_MODE 0x10 +#define TEXT_MODE 0x03 +#define BIOS_VIDEO_INT 0x10 +#define X_MAX 640 /* working screen width */ +#define Y_MAX 348 /* working screen height */ - extern void EVGALine(); +extern void EVGALine(); - /* - * Subroutine to draw a rectangle full of vectors, of the specified - * length and color, around the specified rectangle center. - */ - void VectorsUp(XCenter, YCenter, XLength, YLength, Color) - int XCenter, YCenter; /* center of rectangle to fill */ - int XLength, YLength; /* distance from center to edge - of rectangle */ - int Color; /* color to draw lines in */ - { - int WorkingX, WorkingY; +/* + * Subroutine to draw a rectangle full of vectors, of the specified + * length and color, around the specified rectangle center. + */ +void VectorsUp(XCenter, YCenter, XLength, YLength, Color) +int XCenter, YCenter; /* center of rectangle to fill */ +int XLength, YLength; /* distance from center to edge + of rectangle */ +int Color; /* color to draw lines in */ +{ + int WorkingX, WorkingY; - /* Lines from center to top of rectangle */ - WorkingX = XCenter - XLength; - WorkingY = YCenter - YLength; - for ( ; WorkingX < ( XCenter + XLength ); WorkingX++ ) - EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color); + /* Lines from center to top of rectangle */ + WorkingX = XCenter - XLength; + WorkingY = YCenter - YLength; + for ( ; WorkingX < ( XCenter + XLength ); WorkingX++ ) + EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color); - /* Lines from center to right of rectangle */ - WorkingX = XCenter + XLength - 1; - WorkingY = YCenter - YLength; - for ( ; WorkingY < ( YCenter + YLength ); WorkingY++ ) - EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color); + /* Lines from center to right of rectangle */ + WorkingX = XCenter + XLength - 1; + WorkingY = YCenter - YLength; + for ( ; WorkingY < ( YCenter + YLength ); WorkingY++ ) + EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color); - /* Lines from center to bottom of rectangle */ - WorkingX = XCenter + XLength - 1; - WorkingY = YCenter + YLength - 1; - for ( ; WorkingX >= ( XCenter - XLength ); WorkingX— ) - EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color); + /* Lines from center to bottom of rectangle */ + WorkingX = XCenter + XLength - 1; + WorkingY = YCenter + YLength - 1; + for ( ; WorkingX >= ( XCenter - XLength ); WorkingX— ) + EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color); - /* Lines from center to left of rectangle */ - WorkingX = XCenter - XLength; - WorkingY = YCenter + YLength - 1; - for ( ; WorkingY >= ( YCenter - YLength ); WorkingY— ) - EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color ); - } + /* Lines from center to left of rectangle */ + WorkingX = XCenter - XLength; + WorkingY = YCenter + YLength - 1; + for ( ; WorkingY >= ( YCenter - YLength ); WorkingY— ) + EVGALine(XCenter, YCenter, WorkingX, WorkingY, Color ); +} - /* - * Sample program to draw four rectangles full of lines. - */ - void main() - { - char temp; +/* + * Sample program to draw four rectangles full of lines. + */ +void main() +{ + char temp; - /* Set graphics mode */ - _AX = GRAPHICS_MODE; - geninterrupt(BIOS_VIDEO_INT); + /* Set graphics mode */ + _AX = GRAPHICS_MODE; + geninterrupt(BIOS_VIDEO_INT); - /* Draw each of four rectangles full of vectors */ - VectorsUp(X_MAX / 4, Y_MAX / 4, X_MAX / 4, - Y_MAX / 4, 1); - VectorsUp(X_MAX * 3 / 4, Y_MAX / 4, X_MAX / 4, - Y_MAX / 4, 2); - VectorsUp(X_MAX / 4, Y_MAX * 3 / 4, X_MAX / 4, - Y_MAX / 4, 3); - VectorsUp(X_MAX * 3 / 4, Y_MAX * 3 / 4, X_MAX / 4, - Y_MAX / 4, 4); + /* Draw each of four rectangles full of vectors */ + VectorsUp(X_MAX / 4, Y_MAX / 4, X_MAX / 4, + Y_MAX / 4, 1); + VectorsUp(X_MAX * 3 / 4, Y_MAX / 4, X_MAX / 4, + Y_MAX / 4, 2); + VectorsUp(X_MAX / 4, Y_MAX * 3 / 4, X_MAX / 4, + Y_MAX / 4, 3); + 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); + /* Wait for the enter key to be pressed */ + scanf("%c", &temp); - /* Return back to text mode */ - _AX = TEXT_MODE; - geninterrupt(BIOS_VIDEO_INT); - } + /* Return back to text mode */ + _AX = TEXT_MODE; + geninterrupt(BIOS_VIDEO_INT); +} +``` #### Looking at EVGALine {#Heading7} diff --git a/35-07.md b/35-07.md index 4eb1dee..d27545d 100644 --- a/35-07.md +++ b/35-07.md @@ -12,374 +12,376 @@ pages: 671-678 **LISTING 35.3 L35-3.ASM** - ; 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| - ; suggested by Jim Mackraz. - ; - ; Assembled with TASM - ; - ; By Michael Abrash - ; - ;**************************************************************** - ; C-compatible line-drawing entry point at _EVGALine. * - ; Near C-callable as: * - ; EVGALine(X0, Y0, X1, Y1, Color); * - ;**************************************************************** - ; +```nasm +; 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| +; suggested by Jim Mackraz. +; +; Assembled with TASM +; +; By Michael Abrash +; +;**************************************************************** +; C-compatible line-drawing entry point at _EVGALine. * +; Near C-callable as: * +; EVGALine(X0, Y0, X1, Y1, Color); * +;**************************************************************** +; - model small - .code + model small + .code - ; - ; Equates. - ; - EVGA_SCREEN_WIDTH_IN_BYTES equ 80 ;memory offset from start of - ; one row to start of next - ; in display memory - EVGA_SCREEN_SEGMENT equ 0a000h ;display memory segment - GC_INDEX equ 3ceh ;Graphics Controller - ; Index register port - SET_RESET_INDEX equ 0 ;indexes of needed - ENABLE_SET_RESET_INDEX equ 1 ; Graphics Controller - BIT_MASK_INDEX equ 8 ; registers +; +; Equates. +; +EVGA_SCREEN_WIDTH_IN_BYTES equ 80 ;memory offset from start of + ; one row to start of next + ; in display memory +EVGA_SCREEN_SEGMENT equ 0a000h ;display memory segment +GC_INDEX equ 3ceh ;Graphics Controller + ; Index register port +SET_RESET_INDEX equ 0 ;indexes of needed +ENABLE_SET_RESET_INDEX equ 1 ; Graphics Controller +BIT_MASK_INDEX equ 8 ; registers - ; - ; Stack frame. - ; - EVGALineParms struc - dw ? ;pushed BP - dw ? ;pushed return address (make double - ; word for far call) - X0 dw ? ;starting X coordinate of line - Y0 dw ? ;starting Y coordinate of line - X1 dw ? ;ending X coordinate of line - Y1 dw ? ;ending Y coordinate of line - Color db ? ;color of line - db ? ;dummy to pad to word size - EVGALineParms ends +; +; Stack frame. +; +EVGALineParms struc + dw ? ;pushed BP + dw ? ;pushed return address (make double + ; word for far call) +X0 dw ? ;starting X coordinate of line +Y0 dw ? ;starting Y coordinate of line +X1 dw ? ;ending X coordinate of line +Y1 dw ? ;ending Y coordinate of line +Color db ? ;color of line + db ? ;dummy to pad to word size +EVGALineParms ends - ;**************************************************************** - ; Line drawing macros. * - ;**************************************************************** +;**************************************************************** +; Line drawing macros. * +;**************************************************************** - ; - ; Macro to loop through length of line, drawing each pixel in turn. - ; Used for case of |DeltaX| >= |DeltaY|. - ; Input: - ; MOVE_LEFT: 1 if DeltaX < 0, 0 else - ; AL: pixel mask for initial pixel - ; BX: |DeltaX| - ; DX: address of GC data register, with index register set to - ; index of Bit Mask register - ; SI: DeltaY - ; ES:DI: display memory address of byte containing initial - ; pixel - ; - LINE1 macro MOVE_LEFT - local LineLoop, MoveXCoord, NextPixel, Line1End - 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 - ; at the start location) - shl si,1 ;DeltaY * 2 - mov bp,si ;error term - sub bp,bx ;error term starts at DeltaY * 2 - DeltaX - shl bx,1 ;DeltaX * 2 - sub si,bx ;DeltaY * 2 - DeltaX * 2 (used in loop) - add bx,si ;DeltaY * 2 (used in loop) - mov ah,al ;set aside pixel mask for initial pixel - ; with AL (the pixel mask accumulator) set - ; for the initial pixel - LineLoop: - ; - ; 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 - ; - ; Advance the Y coordinate, first writing all pixels in the current - ; byte, then move the pixel mask either left or right, depending - ; on MOVE_LEFT. - ; - out dx,al ;set up bit mask for pixels in this byte - xchg byte ptr [di],al - ;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 - add di,EVGA_SCREEN_WIDTH_IN_BYTES ;increment Y coordinate - add bp,si ;adjust error term back down - ; - ; Move pixel mask one pixel (either right or left, depending - ; on MOVE_LEFT), adjusting display memory address when pixel mask wraps. - ; - if MOVE_LEFT - rol ah,1 ;move pixel mask 1 pixel to the left - else - ror ah,1 ;move pixel mask 1 pixel to the right - endif - 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 - ; on MOVE_LEFT), adjusting display memory address and writing pixels - ; in this byte when pixel mask wraps. - ; - MoveXCoord: - add bp,bx ;increment error term & keep same - if MOVE_LEFT - rol ah,1 ;move pixel mask 1 pixel to the left - else - ror ah,1 ;move pixel mask 1 pixel to the right - endif - jnc NextPixel ;if still in same byte, no need to - ; modify display memory yet - out dx,al ;set up bit mask for pixels in this byte. - xchg byte ptr [di],al - ;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 - MoveToNextByte: - if MOVE_LEFT - dec di ;next pixel is in byte to left - else - inc di ;next pixel is in byte to right - endif - ResetBitMaskAccumulator: - sub al,al ;reset pixel mask accumulator - NextPixel: - or al,ah ;add the next pixel to the pixel mask - ; accumulator - loop LineLoop - ; - ; Write the pixels in the final byte. - ; - Line1End: - out dx,al ;set up bit mask for pixels in this byte - xchg byte ptr [di],al - ;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 - endm +; +; Macro to loop through length of line, drawing each pixel in turn. +; Used for case of |DeltaX| >= |DeltaY|. +; Input: +; MOVE_LEFT: 1 if DeltaX < 0, 0 else +; AL: pixel mask for initial pixel +; BX: |DeltaX| +; DX: address of GC data register, with index register set to +; index of Bit Mask register +; SI: DeltaY +; ES:DI: display memory address of byte containing initial +; pixel +; +LINE1 macro MOVE_LEFT + local LineLoop, MoveXCoord, NextPixel, Line1End + 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 + ; at the start location) + shl si,1 ;DeltaY * 2 + mov bp,si ;error term + sub bp,bx ;error term starts at DeltaY * 2 - DeltaX + shl bx,1 ;DeltaX * 2 + sub si,bx ;DeltaY * 2 - DeltaX * 2 (used in loop) + add bx,si ;DeltaY * 2 (used in loop) + mov ah,al ;set aside pixel mask for initial pixel + ; with AL (the pixel mask accumulator) set + ; for the initial pixel +LineLoop: +; +; 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 +; +; Advance the Y coordinate, first writing all pixels in the current +; byte, then move the pixel mask either left or right, depending +; on MOVE_LEFT. +; + out dx,al ;set up bit mask for pixels in this byte + xchg byte ptr [di],al + ;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 + add di,EVGA_SCREEN_WIDTH_IN_BYTES ;increment Y coordinate + add bp,si ;adjust error term back down +; +; Move pixel mask one pixel (either right or left, depending +; on MOVE_LEFT), adjusting display memory address when pixel mask wraps. +; +if MOVE_LEFT + rol ah,1 ;move pixel mask 1 pixel to the left +else + ror ah,1 ;move pixel mask 1 pixel to the right +endif + 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 +; on MOVE_LEFT), adjusting display memory address and writing pixels +; in this byte when pixel mask wraps. +; +MoveXCoord: + add bp,bx ;increment error term & keep same +if MOVE_LEFT + rol ah,1 ;move pixel mask 1 pixel to the left +else + ror ah,1 ;move pixel mask 1 pixel to the right +endif + jnc NextPixel ;if still in same byte, no need to + ; modify display memory yet + out dx,al ;set up bit mask for pixels in this byte. + xchg byte ptr [di],al + ;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 +MoveToNextByte: +if MOVE_LEFT + dec di ;next pixel is in byte to left +else + inc di ;next pixel is in byte to right +endif +ResetBitMaskAccumulator: + sub al,al ;reset pixel mask accumulator +NextPixel: + or al,ah ;add the next pixel to the pixel mask + ; accumulator + loop LineLoop +; +; Write the pixels in the final byte. +; +Line1End: + out dx,al ;set up bit mask for pixels in this byte + xchg byte ptr [di],al + ;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 + endm - ; - ; Macro to loop through length of line, drawing each pixel in turn. - ; Used for case of DeltaX < DeltaY. - ; Input: - ; MOVE_LEFT: 1 if DeltaX < 0, 0 else - ; AL: pixel mask for initial pixel - ; BX: |DeltaX| - ; DX: address of GC data register, with index register set to - ; index of Bit Mask register - ; SI: DeltaY - ; ES:DI: display memory address of byte containing initial - ; pixel - ; - LINE2 macro MOVE_LEFT - local LineLoop, MoveYCoord, ETermAction, Line2End - mov cx,si ;# of pixels in line - jcxz Line2End ;done if there are no more pixels - shl bx,1 ;DeltaX * 2 - mov bp,bx ;error term - sub bp,si ;error term starts at DeltaX * 2 - DeltaY - shl si,1 ;DeltaY * 2 - sub bx,si ;DeltaX * 2 - DeltaY * 2 (used in loop) - add si,bx ;DeltaX * 2 (used in loop) - ; - ; Set up initial bit mask & write initial pixel. - ; - out dx,al - xchg byte ptr [di],ah - ;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 - LineLoop: - ; - ; 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 - add bp,si ;increment error term & keep same - jmp short MoveYCoord ; X coordinate - ETermAction: - ; - ; Move pixel mask one pixel (either right or left, depending - ; on MOVE_LEFT), adjusting display memory address when pixel mask wraps. - ; - if MOVE_LEFT - rol al,1 - sbb di,0 - else - ror al,1 - adc di,0 - endif - out dx,al ;set new bit mask - add bp,bx ;adjust error term back down - ; - ; Advance Y coordinate. - ; - MoveYCoord: - add di,EVGA_SCREEN_WIDTH_IN_BYTES - ; - ; Write the next pixel. - ; - xchg byte ptr [di],ah - ;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 - ; - loop LineLoop - Line2End: - endm +; +; Macro to loop through length of line, drawing each pixel in turn. +; Used for case of DeltaX < DeltaY. +; Input: +; MOVE_LEFT: 1 if DeltaX < 0, 0 else +; AL: pixel mask for initial pixel +; BX: |DeltaX| +; DX: address of GC data register, with index register set to +; index of Bit Mask register +; SI: DeltaY +; ES:DI: display memory address of byte containing initial +; pixel +; +LINE2 macro MOVE_LEFT + local LineLoop, MoveYCoord, ETermAction, Line2End + mov cx,si ;# of pixels in line + jcxz Line2End ;done if there are no more pixels + shl bx,1 ;DeltaX * 2 + mov bp,bx ;error term + sub bp,si ;error term starts at DeltaX * 2 - DeltaY + shl si,1 ;DeltaY * 2 + sub bx,si ;DeltaX * 2 - DeltaY * 2 (used in loop) + add si,bx ;DeltaX * 2 (used in loop) +; +; Set up initial bit mask & write initial pixel. +; + out dx,al + xchg byte ptr [di],ah + ;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 +LineLoop: +; +; 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 + add bp,si ;increment error term & keep same + jmp short MoveYCoord ; X coordinate +ETermAction: +; +; Move pixel mask one pixel (either right or left, depending +; on MOVE_LEFT), adjusting display memory address when pixel mask wraps. +; +if MOVE_LEFT + rol al,1 + sbb di,0 +else + ror al,1 + adc di,0 +endif + out dx,al ;set new bit mask + add bp,bx ;adjust error term back down +; +; Advance Y coordinate. +; +MoveYCoord: + add di,EVGA_SCREEN_WIDTH_IN_BYTES +; +; Write the next pixel. +; + xchg byte ptr [di],ah + ;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 +; + loop LineLoop +Line2End: + endm - ;**************************************************************** - ; Line drawing routine. * - ;**************************************************************** +;**************************************************************** +; Line drawing routine. * +;**************************************************************** - public _EVGALine - _EVGALine proc near - push bp - mov bp,sp - push si ;preserve register variables - push di - push ds - ; - ; Point DS to display memory. - ; - mov ax,EVGA_SCREEN_SEGMENT - mov ds,ax - ; - ; Set the Set/Reset and Set/Reset Enable registers for - ; the selected color. - ; - mov dx,GC_INDEX - mov al,SET_RESET_INDEX - out dx,al - inc dx - mov al,[bp+Color] - out dx,al - dec dx - mov al,ENABLE_SET_RESET_INDEX - out dx,al - inc dx - mov al,0ffh - out dx,al - ; - ; Get DeltaY. - ; - mov si,[bp+Y1] ;line Y start - 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 - ; - ; 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 - ; in calculating the start address - mov dx,[bp+X0] - xchg dx,[bp+X1] - mov [bp+X0],dx ;swap X coordinates - neg si ;convert to positive DeltaY - ; - ; Calculate the starting address in display memory of the line. - ; Hardwired for a screen width of 80 bytes. - ; - CalcStartAddress: - shl ax,1 ;Y0 * 2 ;Y0 is already in AX - shl ax,1 ;Y0 * 4 - shl ax,1 ;Y0 * 8 - shl ax,1 ;Y0 * 16 - mov di,ax - shl ax,1 ;Y0 * 32 - shl ax,1 ;Y0 * 64 - add di,ax ;Y0 * 80 - mov dx,[bp+X0] - mov cl,dl ;set aside lower 3 bits of column for - and cl,7 ; pixel masking - shr dx,1 - shr dx,1 - shr dx,1 ;get byte address of column (X0/8) - add di,dx ;offset of line start in display segment - ; - ; Set up GC Index register to point to the Bit Mask register. - ; - mov dx,GC_INDEX - mov al,BIT_MASK_INDEX - out dx,al - inc dx ;leave DX pointing to the GC Data register - ; - ; Set up pixel mask (in-byte pixel address). - ; - mov al,80h - shr al,cl - ; - ; Calculate DeltaX. - ; - mov bx,[bp+X1] - sub bx,[bp+X0] - ; - ; Handle correct one of four octants. - ; - js NegDeltaX - cmp bx,si - jb Octant1 - ; - ; DeltaX >= DeltaY >= 0. - ; - LINE1 0 - jmp EVGALineDone - ; - ; DeltaY > DeltaX >= 0. - ; - Octant1: - LINE2 0 - jmp short EVGALineDone - ; - NegDeltaX: - neg bx ;|DeltaX| - cmp bx,si - jb Octant2 - ; - ; |DeltaX| >= DeltaY and DeltaX < 0. - ; - LINE1 1 - jmp short EVGALineDone - ; - ; |DeltaX| < DeltaY and DeltaX < 0. - ; - Octant2: - LINE2 1 - ; - EVGALineDone: - ; - ; Restore EVGA state. - ; - mov al,0ffh - out dx,al ;set Bit Mask register to 0ffh - dec dx - mov al,ENABLE_SET_RESET_INDEX - out dx,al - inc dx - sub al,al - out dx,al ;set Enable Set/Reset register to 0 - ; - pop ds - pop di - pop si - pop bp - ret - _EVGALine endp + public _EVGALine +_EVGALine proc near + push bp + mov bp,sp + push si ;preserve register variables + push di + push ds +; +; Point DS to display memory. +; + mov ax,EVGA_SCREEN_SEGMENT + mov ds,ax +; +; Set the Set/Reset and Set/Reset Enable registers for +; the selected color. +; + mov dx,GC_INDEX + mov al,SET_RESET_INDEX + out dx,al + inc dx + mov al,[bp+Color] + out dx,al + dec dx + mov al,ENABLE_SET_RESET_INDEX + out dx,al + inc dx + mov al,0ffh + out dx,al +; +; Get DeltaY. +; + mov si,[bp+Y1] ;line Y start + 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 +; +; 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 + ; in calculating the start address + mov dx,[bp+X0] + xchg dx,[bp+X1] + mov [bp+X0],dx ;swap X coordinates + neg si ;convert to positive DeltaY +; +; Calculate the starting address in display memory of the line. +; Hardwired for a screen width of 80 bytes. +; +CalcStartAddress: + shl ax,1 ;Y0 * 2 ;Y0 is already in AX + shl ax,1 ;Y0 * 4 + shl ax,1 ;Y0 * 8 + shl ax,1 ;Y0 * 16 + mov di,ax + shl ax,1 ;Y0 * 32 + shl ax,1 ;Y0 * 64 + add di,ax ;Y0 * 80 + mov dx,[bp+X0] + mov cl,dl ;set aside lower 3 bits of column for + and cl,7 ; pixel masking + shr dx,1 + shr dx,1 + shr dx,1 ;get byte address of column (X0/8) + add di,dx ;offset of line start in display segment +; +; Set up GC Index register to point to the Bit Mask register. +; + mov dx,GC_INDEX + mov al,BIT_MASK_INDEX + out dx,al + inc dx ;leave DX pointing to the GC Data register +; +; Set up pixel mask (in-byte pixel address). +; + mov al,80h + shr al,cl +; +; Calculate DeltaX. +; + mov bx,[bp+X1] + sub bx,[bp+X0] +; +; Handle correct one of four octants. +; + js NegDeltaX + cmp bx,si + jb Octant1 +; +; DeltaX >= DeltaY >= 0. +; + LINE1 0 + jmp EVGALineDone +; +; DeltaY > DeltaX >= 0. +; +Octant1: + LINE2 0 + jmp short EVGALineDone +; +NegDeltaX: + neg bx ;|DeltaX| + cmp bx,si + jb Octant2 +; +; |DeltaX| >= DeltaY and DeltaX < 0. +; + LINE1 1 + jmp short EVGALineDone +; +; |DeltaX| < DeltaY and DeltaX < 0. +; +Octant2: + LINE2 1 +; +EVGALineDone: +; +; Restore EVGA state. +; + mov al,0ffh + out dx,al ;set Bit Mask register to 0ffh + dec dx + mov al,ENABLE_SET_RESET_INDEX + out dx,al + inc dx + sub al,al + out dx,al ;set Enable Set/Reset register to 0 +; + pop ds + pop di + pop si + pop bp + ret +_EVGALine endp - end + end +``` An explanation of the workings of the code in Listing 35.3 would be a lengthy one, and would be redundant since the basic operation of the diff --git a/36-01.md b/36-01.md index ec79099..177b09f 100644 --- a/36-01.md +++ b/36-01.md @@ -56,38 +56,42 @@ 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: - for (i=0; i 0) - { - WorkingScreenPtr++; - } - else - { - WorkingScreenPtr--; - } - } +```cpp +for (i=0; i 0) + { + WorkingScreenPtr++; + } + else + { + WorkingScreenPtr--; + } +} +``` Here, the programmer knows which way the line is going before the main loop begins—but nonetheless performs that test every time through the loop, when calculating the address of the next pixel. Far better to perform the test only once, outside the loop, as shown here: - if (XDelta > 0) - { - for (i=0; i 0) +{ + for (i=0; i +#include - #define SCREEN_WIDTH 320 - #define SCREEN_SEGMENT 0xA000 +#define SCREEN_WIDTH 320 +#define SCREEN_SEGMENT 0xA000 - void DrawHorizontalRun(char far **ScreenPtr, int XAdvance, int RunLength, - int Color); - void DrawVerticalRun(char far **ScreenPtr, int XAdvance, int RunLength, - int Color); - /* Draws a line between the specified endpoints in color Color. */ - void LineDraw(int XStart, int YStart, int XEnd, int YEnd, int Color) - { - int Temp, AdjUp, AdjDown, ErrorTerm, XAdvance, XDelta, YDelta; - int WholeStep, InitialPixelCount, FinalPixelCount, i, RunLength; - char far *ScreenPtr; +void DrawHorizontalRun(char far **ScreenPtr, int XAdvance, int RunLength, + int Color); +void DrawVerticalRun(char far **ScreenPtr, int XAdvance, int RunLength, + int Color); +/* Draws a line between the specified endpoints in color Color. */ +void LineDraw(int XStart, int YStart, int XEnd, int YEnd, int Color) +{ + int Temp, AdjUp, AdjDown, ErrorTerm, XAdvance, XDelta, YDelta; + int WholeStep, InitialPixelCount, FinalPixelCount, i, RunLength; + char far *ScreenPtr; - /* We'll always draw top to bottom, to reduce the number of cases we have to - handle, and to make lines between the same endpoints draw the same pixels */ - if (YStart > YEnd) { - Temp = YStart; - YStart = YEnd; - YEnd = Temp; - Temp = XStart; - XStart = XEnd; - XEnd = Temp; - } - /* Point to the bitmap address first pixel to draw */ - ScreenPtr = MK_FP(SCREEN_SEGMENT, YStart * SCREEN_WIDTH + XStart); + /* We'll always draw top to bottom, to reduce the number of cases we have to + handle, and to make lines between the same endpoints draw the same pixels */ + if (YStart > YEnd) { + Temp = YStart; + YStart = YEnd; + YEnd = Temp; + Temp = XStart; + XStart = XEnd; + XEnd = Temp; + } + /* 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 - going horizontally */ - if ((XDelta = XEnd - XStart) < 0) - { - XAdvance = -1; - XDelta = -XDelta; - } - else - { - XAdvance = 1; - } - /* Figure out how far we're going vertically */ - YDelta = YEnd - YStart; + /* Figure out whether we're going left or right, and how far we're + going horizontally */ + if ((XDelta = XEnd - XStart) < 0) + { + XAdvance = -1; + XDelta = -XDelta; + } + else + { + XAdvance = 1; + } + /* Figure out how far we're going vertically */ + YDelta = YEnd - YStart; - /* Special-case horizontal, vertical, and diagonal lines, for speed - and to avoid nasty boundary conditions and division by 0 */ - if (XDelta == 0) - { - /* Vertical line */ - for (i=0; i<=YDelta; i++) - { - *ScreenPtr = Color; - ScreenPtr += SCREEN_WIDTH; - } - return; - } - if (YDelta == 0) - { - /* Horizontal line */ - for (i=0; i<=XDelta; i++) - { - *ScreenPtr = Color; - ScreenPtr += XAdvance; - } - return; - } - if (XDelta == YDelta) - { - /* Diagonal line */ - for (i=0; i<=XDelta; i++) - { - *ScreenPtr = Color; - ScreenPtr += XAdvance + SCREEN_WIDTH; - } - return; - } + /* Special-case horizontal, vertical, and diagonal lines, for speed + and to avoid nasty boundary conditions and division by 0 */ + if (XDelta == 0) + { + /* Vertical line */ + for (i=0; i<=YDelta; i++) + { + *ScreenPtr = Color; + ScreenPtr += SCREEN_WIDTH; + } + return; + } + if (YDelta == 0) + { + /* Horizontal line */ + for (i=0; i<=XDelta; i++) + { + *ScreenPtr = Color; + ScreenPtr += XAdvance; + } + return; + } + if (XDelta == YDelta) + { + /* Diagonal line */ + for (i=0; i<=XDelta; i++) + { + *ScreenPtr = Color; + ScreenPtr += XAdvance + SCREEN_WIDTH; + } + return; + } - /* Determine whether the line is X or Y major, and handle accordingly */ - if (XDelta >= YDelta) - { - /* X major line */ - /* Minimum # of pixels in a run in this line */ - WholeStep = XDelta / YDelta; + /* Determine whether the line is X or Y major, and handle accordingly */ + if (XDelta >= YDelta) + { + /* X major line */ + /* Minimum # of pixels in a run in this line */ + WholeStep = XDelta / YDelta; - /* Error term adjust each time Y steps by 1; used to tell when one - extra pixel should be drawn as part of a run, to account for - fractional steps along the X axis per 1-pixel steps along Y */ - AdjUp = (XDelta % YDelta) * 2; + /* Error term adjust each time Y steps by 1; used to tell when one + extra pixel should be drawn as part of a run, to account for + fractional steps along the X axis per 1-pixel steps along Y */ + AdjUp = (XDelta % YDelta) * 2; - /* Error term adjust when the error term turns over, used to factor - out the X step made at that time */ - AdjDown = YDelta * 2; + /* Error term adjust when the error term turns over, used to factor + out the X step made at that time */ + AdjDown = YDelta * 2; - /* Initial error term; reflects an initial step of 0.5 along the Y - axis */ - ErrorTerm = (XDelta % YDelta) - (YDelta * 2); + /* Initial error term; reflects an initial step of 0.5 along the Y + axis */ + ErrorTerm = (XDelta % YDelta) - (YDelta * 2); - /* The initial and last runs are partial, because Y advances only 0.5 - for these runs, rather than 1. Divide one full run, plus the - initial pixel, between the initial and last runs */ - InitialPixelCount = (WholeStep / 2) + 1; - FinalPixelCount = InitialPixelCount; + /* The initial and last runs are partial, because Y advances only 0.5 + for these runs, rather than 1. Divide one full run, plus the + initial pixel, between the initial and last runs */ + InitialPixelCount = (WholeStep / 2) + 1; + FinalPixelCount = InitialPixelCount; - /* 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 ((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 - to error term so this pixel will be handled by the normal full-run loop */ - if ((WholeStep & 0x01) != 0) - { - ErrorTerm += YDelta; - } - /* Draw the first, partial run of pixels */ - DrawHorizontalRun(&ScreenPtr, XAdvance, InitialPixelCount, Color); - /* Draw all full runs */ - for (i=0; i<(YDelta-1); i++) - { - RunLength = WholeStep; /* run is at least this long */ - /* Advance the error term and add an extra pixel if the error - term so indicates */ - if ((ErrorTerm += AdjUp) > 0) - { - RunLength++; - ErrorTerm -= AdjDown; /* reset the error term */ - } - /* Draw this scan line's run */ - DrawHorizontalRun(&ScreenPtr, XAdvance, RunLength, Color); - } - /* Draw the final run of pixels */ - DrawHorizontalRun(&ScreenPtr, XAdvance, FinalPixelCount, Color); - return; - } - else - { - /* Y major line */ + /* 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 ((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 + to error term so this pixel will be handled by the normal full-run loop */ + if ((WholeStep & 0x01) != 0) + { + ErrorTerm += YDelta; + } + /* Draw the first, partial run of pixels */ + DrawHorizontalRun(&ScreenPtr, XAdvance, InitialPixelCount, Color); + /* Draw all full runs */ + for (i=0; i<(YDelta-1); i++) + { + RunLength = WholeStep; /* run is at least this long */ + /* Advance the error term and add an extra pixel if the error + term so indicates */ + if ((ErrorTerm += AdjUp) > 0) + { + RunLength++; + ErrorTerm -= AdjDown; /* reset the error term */ + } + /* Draw this scan line's run */ + DrawHorizontalRun(&ScreenPtr, XAdvance, RunLength, Color); + } + /* Draw the final run of pixels */ + DrawHorizontalRun(&ScreenPtr, XAdvance, FinalPixelCount, Color); + return; + } + else + { + /* Y major line */ - /* Minimum # of pixels in a run in this line */ - WholeStep = YDelta / XDelta; + /* Minimum # of pixels in a run in this line */ + WholeStep = YDelta / XDelta; - /* Error term adjust each time X steps by 1; used to tell when 1 extra - pixel should be drawn as part of a run, to account for - fractional steps along the Y axis per 1-pixel steps along X */ - AdjUp = (YDelta % XDelta) * 2; + /* Error term adjust each time X steps by 1; used to tell when 1 extra + pixel should be drawn as part of a run, to account for + fractional steps along the Y axis per 1-pixel steps along X */ + AdjUp = (YDelta % XDelta) * 2; - /* Error term adjust when the error term turns over, used to factor - out the Y step made at that time */ - AdjDown = XDelta * 2; + /* Error term adjust when the error term turns over, used to factor + out the Y step made at that time */ + AdjDown = XDelta * 2; - /* Initial error term; reflects initial step of 0.5 along the X axis */ - ErrorTerm = (YDelta % XDelta) - (XDelta * 2); + /* Initial error term; reflects initial step of 0.5 along the X axis */ + ErrorTerm = (YDelta % XDelta) - (XDelta * 2); - /* The initial and last runs are partial, because X advances only 0.5 - for these runs, rather than 1. Divide one full run, plus the - initial pixel, between the initial and last runs */ - InitialPixelCount = (WholeStep / 2) + 1; - FinalPixelCount = InitialPixelCount; + /* The initial and last runs are partial, because X advances only 0.5 + for these runs, rather than 1. Divide one full run, plus the + initial pixel, between the initial and last runs */ + InitialPixelCount = (WholeStep / 2) + 1; + FinalPixelCount = InitialPixelCount; - /* 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 */ - 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 - handled by the normal full-run loop */ - if ((WholeStep & 0x01) != 0) - { - ErrorTerm += XDelta; - } - /* Draw the first, partial run of pixels */ - DrawVerticalRun(&ScreenPtr, XAdvance, InitialPixelCount, Color); + /* 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 */ + 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 + handled by the normal full-run loop */ + if ((WholeStep & 0x01) != 0) + { + ErrorTerm += XDelta; + } + /* Draw the first, partial run of pixels */ + DrawVerticalRun(&ScreenPtr, XAdvance, InitialPixelCount, Color); - /* Draw all full runs */ - for (i=0; i<(XDelta-1); i++) - { - RunLength = WholeStep; /* run is at least this long */ - /* Advance the error term and add an extra pixel if the error - term so indicates */ - if ((ErrorTerm += AdjUp) > 0) - { - RunLength++; - ErrorTerm -= AdjDown; /* reset the error term */ - } - /* Draw this scan line's run */ - DrawVerticalRun(&ScreenPtr, XAdvance, RunLength, Color); - } - /* Draw the final run of pixels */ - DrawVerticalRun(&ScreenPtr, XAdvance, FinalPixelCount, Color); - return; - } - } - /* Draws a horizontal run of pixels, then advances the bitmap pointer to - the first pixel of the next run. */ - void DrawHorizontalRun(char far **ScreenPtr, int XAdvance, - int RunLength, int Color) - { - int i; - char far *WorkingScreenPtr = *ScreenPtr; + /* Draw all full runs */ + for (i=0; i<(XDelta-1); i++) + { + RunLength = WholeStep; /* run is at least this long */ + /* Advance the error term and add an extra pixel if the error + term so indicates */ + if ((ErrorTerm += AdjUp) > 0) + { + RunLength++; + ErrorTerm -= AdjDown; /* reset the error term */ + } + /* Draw this scan line's run */ + DrawVerticalRun(&ScreenPtr, XAdvance, RunLength, Color); + } + /* Draw the final run of pixels */ + DrawVerticalRun(&ScreenPtr, XAdvance, FinalPixelCount, Color); + return; + } +} +/* Draws a horizontal run of pixels, then advances the bitmap pointer to + the first pixel of the next run. */ +void DrawHorizontalRun(char far **ScreenPtr, int XAdvance, + int RunLength, int Color) +{ + int i; + char far *WorkingScreenPtr = *ScreenPtr; - for (i=0; i +#include - #define GRAPHICS_MODE 0x13 - #define TEXT_MODE 0x03 - #define BIOS_VIDEO_INT 0x10 - #define X_MAX 320 /* working screen width */ - #define Y_MAX 200 /* working screen height */ +#define GRAPHICS_MODE 0x13 +#define TEXT_MODE 0x03 +#define BIOS_VIDEO_INT 0x10 +#define X_MAX 320 /* working screen width */ +#define Y_MAX 200 /* working screen height */ - extern void LineDraw(int XStart, int YStart, int XEnd, int YEnd, int Color); +extern void LineDraw(int XStart, int YStart, int XEnd, int YEnd, int Color); - /* Subroutine to draw a rectangle full of vectors, of the specified - * length and color, around the specified rectangle center. */ - void VectorsUp(XCenter, YCenter, XLength, YLength, Color) - int XCenter, YCenter; /* center of rectangle to fill */ - int XLength, YLength; /* distance from center to edge of rectangle */ - int Color; /* color to draw lines in */ - { - int WorkingX, WorkingY; +/* Subroutine to draw a rectangle full of vectors, of the specified + * length and color, around the specified rectangle center. */ +void VectorsUp(XCenter, YCenter, XLength, YLength, Color) +int XCenter, YCenter; /* center of rectangle to fill */ +int XLength, YLength; /* distance from center to edge of rectangle */ +int Color; /* color to draw lines in */ +{ + int WorkingX, WorkingY; - /* lines from center to top of rectangle */ - WorkingX = XCenter - XLength; - WorkingY = YCenter - YLength; - for ( ; WorkingX < ( XCenter + XLength ); WorkingX++ ) - { - LineDraw(XCenter, YCenter, WorkingX, WorkingY, Color); - } - /* lines from center to right of rectangle */ - WorkingX = XCenter + XLength - 1; - WorkingY = YCenter - YLength; - for ( ; WorkingY < ( YCenter + YLength ); WorkingY++ ) - { - LineDraw(XCenter, YCenter, WorkingX, WorkingY, Color); - } - /* lines from center to bottom of rectangle */ - WorkingX = XCenter + XLength - 1; - WorkingY = YCenter + YLength - 1; - for ( ; WorkingX >= ( XCenter - XLength ); WorkingX-- ) - { - LineDraw(XCenter, YCenter, WorkingX, WorkingY, Color); - } - /* lines from center to left of rectangle */ - WorkingX = XCenter - XLength; - WorkingY = YCenter + YLength - 1; - for ( ; WorkingY >= ( YCenter - YLength ); WorkingY-- ) - { - LineDraw(XCenter, YCenter, WorkingX, WorkingY, Color); - } - } - /* Sample program to draw four rectangles full of lines. */ - int main() - { - union REGS regs; + /* lines from center to top of rectangle */ + WorkingX = XCenter - XLength; + WorkingY = YCenter - YLength; + for ( ; WorkingX < ( XCenter + XLength ); WorkingX++ ) + { + LineDraw(XCenter, YCenter, WorkingX, WorkingY, Color); + } + /* lines from center to right of rectangle */ + WorkingX = XCenter + XLength - 1; + WorkingY = YCenter - YLength; + for ( ; WorkingY < ( YCenter + YLength ); WorkingY++ ) + { + LineDraw(XCenter, YCenter, WorkingX, WorkingY, Color); + } + /* lines from center to bottom of rectangle */ + WorkingX = XCenter + XLength - 1; + WorkingY = YCenter + YLength - 1; + for ( ; WorkingX >= ( XCenter - XLength ); WorkingX-- ) + { + LineDraw(XCenter, YCenter, WorkingX, WorkingY, Color); + } + /* lines from center to left of rectangle */ + WorkingX = XCenter - XLength; + WorkingY = YCenter + YLength - 1; + for ( ; WorkingY >= ( YCenter - YLength ); WorkingY-- ) + { + LineDraw(XCenter, YCenter, WorkingX, WorkingY, Color); + } +} +/* Sample program to draw four rectangles full of lines. */ +int main() +{ + union REGS regs; - /* Set graphics mode */ - regs.x.ax = GRAPHICS_MODE; - int86(BIOS_VIDEO_INT, ®s, ®s); + /* Set graphics mode */ + regs.x.ax = GRAPHICS_MODE; + int86(BIOS_VIDEO_INT, ®s, ®s); - /* Draw each of four rectangles full of vectors */ - VectorsUp(X_MAX / 4, Y_MAX / 4, X_MAX / 4, Y_MAX / 4, 1); - VectorsUp(X_MAX * 3 / 4, Y_MAX / 4, X_MAX / 4, Y_MAX / 4, 2); - VectorsUp(X_MAX / 4, Y_MAX * 3 / 4, X_MAX / 4, Y_MAX / 4, 3); - VectorsUp(X_MAX * 3 / 4, Y_MAX * 3 / 4, X_MAX / 4, Y_MAX / 4, 4); + /* Draw each of four rectangles full of vectors */ + VectorsUp(X_MAX / 4, Y_MAX / 4, X_MAX / 4, Y_MAX / 4, 1); + VectorsUp(X_MAX * 3 / 4, Y_MAX / 4, X_MAX / 4, Y_MAX / 4, 2); + VectorsUp(X_MAX / 4, Y_MAX * 3 / 4, X_MAX / 4, Y_MAX / 4, 3); + VectorsUp(X_MAX * 3 / 4, Y_MAX * 3 / 4, X_MAX / 4, Y_MAX / 4, 4); - /* Wait for a key to be pressed */ - getch(); + /* Wait for a key to be pressed */ + getch(); - /* Return back to text mode */ - regs.x.ax = TEXT_MODE; - int86(BIOS_VIDEO_INT, ®s, ®s); - } + /* Return back to text mode */ + regs.x.ax = TEXT_MODE; + int86(BIOS_VIDEO_INT, ®s, ®s); +} +``` diff --git a/37-01.md b/37-01.md index 6d50149..9d23c13 100644 --- a/37-01.md +++ b/37-01.md @@ -67,351 +67,353 @@ 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 - ; 320x200 256-color mode. - ; Draws a line between the specified endpoints in color Color. - ; C near-callable as: - ; void LineDraw(int XStart, int YStart, int XEnd, int YEnd, int Color) - ; Tested with TASM +```nasm +; 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: +; void LineDraw(int XStart, int YStart, int XEnd, int YEnd, int Color) +; Tested with TASM - SCREEN_WIDTH equ 320 - SCREEN_SEGMENT equ 0a000h - .model small - .code +SCREEN_WIDTH equ 320 +SCREEN_SEGMENT equ 0a000h + .model small + .code - ; Parameters to call. - parms struc - dw ? ;pushed BP - dw ? ;pushed return address - XStart dw ? ;X start coordinate of line - YStart dw ? ;Y start coordinate of line - XEnd dw ? ;X end coordinate of line - YEnd dw ? ;Y end coordinate of line - Color db ? ;color in which to draw line - db ? ;dummy byte because Color is really a word - parms ends +; Parameters to call. +parms struc + dw ? ;pushed BP + dw ? ;pushed return address +XStart dw ? ;X start coordinate of line +YStart dw ? ;Y start coordinate of line +XEnd dw ? ;X end coordinate of line +YEnd dw ? ;Y end coordinate of line +Color db ? ;color in which to draw line + db ? ;dummy byte because Color is really a word +parms ends - ; Local variables. - AdjUp equ -2 ;error term adjust up on each advance - AdjDown equ -4 ;error term adjust down when error term turns over - WholeStep equ -6 ;minimum run length - XAdvance equ -8 ;1 or -1, for direction in which X advances - LOCAL_SIZE equ 8 - public _LineDraw - _LineDraw proc near - cld - 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, - ; and to make lines between the same endpoints always draw the same pixels. - mov ax,[bp].YStart - cmp ax,[bp].YEnd - jle LineIsTopToBottom - xchg [bp].YEnd,ax; swap endpoints - mov [bp].YStart,ax - movbx, [bp].XStart - xchg [bp].XEnd,bx - mov [bp].XStart,bx - LineIsTopToBottom: - ; Point DI to the first pixel to draw. - mov dx,SCREEN_WIDTH - mul dx ;YStart * SCREEN_WIDTH - mov si,[bp].XStart - 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). - 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 - ; horizontally. In the process, special-case vertical lines, for speed and - ; to avoid nasty boundary conditions and division by 0. - mov dx,[bp].XEnd - sub dx,si ;XDelta - jnz NotVerticalLine ;XDelta == 0 means vertical line - ;it is a vertical line - ;yes, special case vertical line - mov ax,SCREEN_SEGMENT - mov ds,ax ;point DS:DI to the first byte to draw - mov al,[bp].Color - VLoop: - mov [di],al - add di,SCREEN_WIDTH - dec cx - jns VLoop - jmp Done - ; Special-case code for horizontal lines. - align 2 - IsHorizontalLine: - mov ax,SCREEN_SEGMENT - mov es,ax ;point ES:DI to the first byte to draw - mov al,[bp].Color - mov ah,al ;duplicate in high byte for word access - and bx,bx ;left to right? - jns DirSet ;yes - sub di,dx ;currently right to left, point to left - ; end so we can go left to right - ; (avoids unpleasantness withright to - ; left REP STOSW) - DirSet: - mov cx,dx - inc cx ;# of pixels to draw - shr cx,1 ;# of words to draw - rep stosw ;do as many words as possible - adc cx,cx - rep stosb ;do the odd byte, if there is one - jmp Done - ; Special-case code for diagonal lines. - align 2 - IsDiagonalLine: - mov ax,SCREEN_SEGMENT - mov ds,ax ;point DS:DI to the first byte to draw - mov al,[bp].Color - add bx,SCREEN_WIDTH ;advance distance from one pixel to next - DLoop: - mov [di],al - add di,bx - dec cx - jns DLoop - jmp Done +; Local variables. +AdjUp equ -2 ;error term adjust up on each advance +AdjDown equ -4 ;error term adjust down when error term turns over +WholeStep equ -6 ;minimum run length +XAdvance equ -8 ;1 or -1, for direction in which X advances +LOCAL_SIZE equ 8 + public _LineDraw +_LineDraw proc near + cld + 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, +; and to make lines between the same endpoints always draw the same pixels. + mov ax,[bp].YStart + cmp ax,[bp].YEnd + jle LineIsTopToBottom + xchg [bp].YEnd,ax; swap endpoints + mov [bp].YStart,ax + movbx, [bp].XStart + xchg [bp].XEnd,bx + mov [bp].XStart,bx +LineIsTopToBottom: +; Point DI to the first pixel to draw. + mov dx,SCREEN_WIDTH + mul dx ;YStart * SCREEN_WIDTH + mov si,[bp].XStart + 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). + 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 +; horizontally. In the process, special-case vertical lines, for speed and +; to avoid nasty boundary conditions and division by 0. + mov dx,[bp].XEnd + sub dx,si ;XDelta + jnz NotVerticalLine ;XDelta == 0 means vertical line + ;it is a vertical line + ;yes, special case vertical line + mov ax,SCREEN_SEGMENT + mov ds,ax ;point DS:DI to the first byte to draw + mov al,[bp].Color +VLoop: + mov [di],al + add di,SCREEN_WIDTH + dec cx + jns VLoop + jmp Done +; Special-case code for horizontal lines. + align 2 +IsHorizontalLine: + mov ax,SCREEN_SEGMENT + mov es,ax ;point ES:DI to the first byte to draw + mov al,[bp].Color + mov ah,al ;duplicate in high byte for word access + and bx,bx ;left to right? + jns DirSet ;yes + sub di,dx ;currently right to left, point to left + ; end so we can go left to right + ; (avoids unpleasantness withright to + ; left REP STOSW) +DirSet: + mov cx,dx + inc cx ;# of pixels to draw + shr cx,1 ;# of words to draw + rep stosw ;do as many words as possible + adc cx,cx + rep stosb ;do the odd byte, if there is one + jmp Done +; Special-case code for diagonal lines. + align 2 +IsDiagonalLine: + mov ax,SCREEN_SEGMENT + mov ds,ax ;point DS:DI to the first byte to draw + mov al,[bp].Color + add bx,SCREEN_WIDTH ;advance distance from one pixel to next +DLoop: + mov [di],al + add di,bx + dec cx + jns DLoop + jmp Done - align 2 - NotVerticalLine: - mov bx,1 ;assume left to right, so XAdvance = 1 - ;***leaves flags unchanged*** - jns LeftToRight ;left to right, all set - neg bx ;right to left, so XAdvance = -1 - neg dx ;|XDelta| - LeftToRight: - ; Special-case horizontal lines. - and cx,cx ;YDelta == 0? - jz IsHorizontalLine ;yes + align 2 +NotVerticalLine: + mov bx,1 ;assume left to right, so XAdvance = 1 + ;***leaves flags unchanged*** + jns LeftToRight ;left to right, all set + neg bx ;right to left, so XAdvance = -1 + neg dx ;|XDelta| +LeftToRight: +; Special-case horizontal lines. + and cx,cx ;YDelta == 0? + jz IsHorizontalLine ;yes - ; Special-case diagonal lines. - cmp cx,dx ;YDelta == XDelta? - jz IsDiagonalLine ;yes - ; Determine whether the line is X or Y major, and handle accordingly. - cmp dx,cx - jae XMajor - jmp YMajor - ; X-major (more horizontal than vertical) line. - align 2 - XMajor: - mov ax,SCREEN_SEGMENT - mov es,ax ;point ES:DI to the first byte to draw - and bx,bx ;left to right? - jns DFSet ;yes, CLD is already set - std ;right to left, so draw backwards - DFSet: - mov ax,dx ;XDelta - sub dx,dx ;prepare for division - div cx ;AX = XDelta/YDelta - ; (minimum # of pixels in a run in this line) - ;DX = XDelta % YDelta - mov bx,dx ;error term adjust each time Y steps by 1; - add bx,bx ; used to tell when one extra pixel should be - mov [bp].AdjUp,bx ; drawn as part of a run, to account for - ; fractional steps along the X axis per - ; 1-pixel steps along Y - mov si,cx ;error term adjust when the error term turns - add si,si ; over, used to factor out the X step made at - mov [bp].AdjDown,si ; that time - ; Initial error term; reflects an initial step of 0.5 along the Y axis. - sub dx,si ;(XDelta % YDelta) - (YDelta * 2) - ;DX = initial error term - ; The initial and last runs are partial, because Y advances only 0.5 for - ; these runs, rather than 1. Divide one full run, plus the initial pixel, - ; between the initial and last runs. - mov si,cx ;SI = YDelta - mov cx,ax ;whole step (minimum run length) - shr cx,1 - inc cx ;initial pixel count = (whole step / 2) + 1; - ; (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 - ; 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 - ; 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) - test al,1 ;is run length even? - 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, - ; because of the above test) - dec cx ;both conditions met; make initial run 1 - ; shorter - XMajorAdjustDone: - mov [bp].WholeStep,ax ;whole step (minimum run length) - mov al,[bp].Color ;AL = drawing color - ; Draw the first, partial run of pixels. - rep stosb ;draw the final run - add di,SCREEN_WIDTH ;advance along the minor axis (Y) - ; Draw all full runs. - cmp si,1 ;are there more than 2 scans, so there are - ; some full runs? (SI = # scans - 1) - jna XMajorDrawLast ;no, no full runs - dec dx ;adjust error term by -1 so we can use - ; carry test - shr si,1 ;convert from scan to scan-pair count - jnc XMajorFullRunsOddEntry ;if there is an odd umber of scans, - ; do the odd scan now - XMajorFullRunsLoop: - mov cx,[bp].WholeStep ;run is at least this long - add dx,bx ;advance the error term and add an extra - jnc XMajorNoExtra ; pixel if the error term so indicates - inc cx ;one extra pixel in run - sub dx,[bp].AdjDown ;reset the error term - XMajorNoExtra: - 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 - mov cx,[bp].WholeStep ;run is at least this long - add dx,bx ;advance the error term and add an extra - jnc XMajorNoExtra2 ; pixel if the error term so indicates - inc cx ;one extra pixel in run - sub dx,[bp].AdjDown ;reset the error term - XMajorNoExtra2: - rep stosb ;draw this scan line's run - add di,SCREEN_WIDTH ;advance along the minor axis (Y) +; Special-case diagonal lines. + cmp cx,dx ;YDelta == XDelta? + jz IsDiagonalLine ;yes +; Determine whether the line is X or Y major, and handle accordingly. + cmp dx,cx + jae XMajor + jmp YMajor +; X-major (more horizontal than vertical) line. + align 2 +XMajor: + mov ax,SCREEN_SEGMENT + mov es,ax ;point ES:DI to the first byte to draw + and bx,bx ;left to right? + jns DFSet ;yes, CLD is already set + std ;right to left, so draw backwards +DFSet: + mov ax,dx ;XDelta + sub dx,dx ;prepare for division + div cx ;AX = XDelta/YDelta + ; (minimum # of pixels in a run in this line) + ;DX = XDelta % YDelta + mov bx,dx ;error term adjust each time Y steps by 1; + add bx,bx ; used to tell when one extra pixel should be + mov [bp].AdjUp,bx ; drawn as part of a run, to account for + ; fractional steps along the X axis per + ; 1-pixel steps along Y + mov si,cx ;error term adjust when the error term turns + add si,si ; over, used to factor out the X step made at + mov [bp].AdjDown,si ; that time +; Initial error term; reflects an initial step of 0.5 along the Y axis. + sub dx,si ;(XDelta % YDelta) - (YDelta * 2) + ;DX = initial error term +; The initial and last runs are partial, because Y advances only 0.5 for +; these runs, rather than 1. Divide one full run, plus the initial pixel, +; between the initial and last runs. + mov si,cx ;SI = YDelta + mov cx,ax ;whole step (minimum run length) + shr cx,1 + inc cx ;initial pixel count = (whole step / 2) + 1; + ; (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 +; 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 +; 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) + test al,1 ;is run length even? + 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, + ; because of the above test) + dec cx ;both conditions met; make initial run 1 + ; shorter +XMajorAdjustDone: + mov [bp].WholeStep,ax ;whole step (minimum run length) + mov al,[bp].Color ;AL = drawing color +; Draw the first, partial run of pixels. + rep stosb ;draw the final run + add di,SCREEN_WIDTH ;advance along the minor axis (Y) +; Draw all full runs. + cmp si,1 ;are there more than 2 scans, so there are + ; some full runs? (SI = # scans - 1) + jna XMajorDrawLast ;no, no full runs + dec dx ;adjust error term by -1 so we can use + ; carry test + shr si,1 ;convert from scan to scan-pair count + jnc XMajorFullRunsOddEntry ;if there is an odd umber of scans, + ; do the odd scan now +XMajorFullRunsLoop: + mov cx,[bp].WholeStep ;run is at least this long + add dx,bx ;advance the error term and add an extra + jnc XMajorNoExtra ; pixel if the error term so indicates + inc cx ;one extra pixel in run + sub dx,[bp].AdjDown ;reset the error term +XMajorNoExtra: + 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 + mov cx,[bp].WholeStep ;run is at least this long + add dx,bx ;advance the error term and add an extra + jnc XMajorNoExtra2 ; pixel if the error term so indicates + inc cx ;one extra pixel in run + sub dx,[bp].AdjDown ;reset the error term +XMajorNoExtra2: + rep stosb ;draw this scan line's run + add di,SCREEN_WIDTH ;advance along the minor axis (Y) - dec si - jnz XMajorFullRunsLoop - ; Draw the final run of pixels. - XMajorDrawLast: - pop cx ;get back the final run pixel length - rep stosb ;draw the final run + dec si + jnz XMajorFullRunsLoop +; Draw the final run of pixels. +XMajorDrawLast: + pop cx ;get back the final run pixel length + rep stosb ;draw the final run - cld ;restore normal direction flag - jmp Done - ; Y-major (more vertical than horizontal) line. - align 2 - YMajor: - mov [bp].XAdvance,bx ;remember which way X advances - mov ax,SCREEN_SEGMENT - mov ds,ax ;point DS:DI to the first byte to draw - mov ax,cx ;YDelta - mov cx,dx ;XDelta - sub dx,dx ;prepare for division - div cx ;AX = YDelta/XDelta - ; (minimum # of pixels in a run in this line) - ;DX = YDelta % XDelta - mov bx,dx ;error term adjust each time X steps by 1; - add bx,bx ; used to tell when one extra pixel should be - mov [bp].AdjUp,bx ; drawn as part of a run, to account for - ; fractional steps along the Y axis per - ; 1-pixel steps along X - mov si,cx ;error term adjust when the error term turns - add si,si ; over, used to factor out the Y step made at - mov [bp].AdjDown,si ; that time + cld ;restore normal direction flag + jmp Done +; Y-major (more vertical than horizontal) line. + align 2 +YMajor: + mov [bp].XAdvance,bx ;remember which way X advances + mov ax,SCREEN_SEGMENT + mov ds,ax ;point DS:DI to the first byte to draw + mov ax,cx ;YDelta + mov cx,dx ;XDelta + sub dx,dx ;prepare for division + div cx ;AX = YDelta/XDelta + ; (minimum # of pixels in a run in this line) + ;DX = YDelta % XDelta + mov bx,dx ;error term adjust each time X steps by 1; + add bx,bx ; used to tell when one extra pixel should be + mov [bp].AdjUp,bx ; drawn as part of a run, to account for + ; fractional steps along the Y axis per + ; 1-pixel steps along X + mov si,cx ;error term adjust when the error term turns + add si,si ; over, used to factor out the Y step made at + mov [bp].AdjDown,si ; that time - ; Initial error term; reflects an initial step of 0.5 along the X axis. - sub dx,si ;(YDelta % XDelta) - (XDelta * 2) - ;DX = initial error term - ; The initial and last runs are partial, because X advances only 0.5 for - ; these runs, rather than 1. Divide one full run, plus the initial pixel, - ; between the initial and last runs. - mov si,cx ;SI = XDelta - mov cx,ax ;whole step (minimum run length) - shr cx,1 - inc cx ;initial pixel count = (whole step / 2) + 1; - ; (may be adjusted later) - push cx ;remember final run pixel count for later +; Initial error term; reflects an initial step of 0.5 along the X axis. + sub dx,si ;(YDelta % XDelta) - (XDelta * 2) + ;DX = initial error term +; The initial and last runs are partial, because X advances only 0.5 for +; these runs, rather than 1. Divide one full run, plus the initial pixel, +; between the initial and last runs. + mov si,cx ;SI = XDelta + mov cx,ax ;whole step (minimum run length) + shr cx,1 + inc cx ;initial pixel count = (whole step / 2) + 1; + ; (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 - ; 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 - ; 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, - ; because of the above test) - dec cx ;both conditions met; make initial run 1 - ; shorter - YMajorAdjustDone: - mov [bp].WholeStep,ax ;whole step (minimum run length) - mov al,[bp].Color ;AL = drawing color - mov bx,[bp].XAdvance ;which way X advances - ; Draw the first, partial run of pixels. - YMajorFirstLoop: - mov [di],al ;draw the pixel - add di,SCREEN_WIDTH ;advance along the major axis (Y) - dec cx - jnz YMajorFirstLoop - add di,bx ;advance along the minor axis (X) - ; Draw all full runs. - cmp si,1 ;# of full runs. Are there more than 2 - ; columns, so there are some full runs? - ; (SI = # columns - 1) - jna YMajorDrawLast ;no, no full runs - dec dx ;adjust error term by -1 so we can use - ; carry test - shr si,1 ;convert from column to column-pair count - jnc YMajorFullRunsOddEntry ;if there is an odd number of - ; columns, do the odd column now - YMajorFullRunsLoop: - mov cx,[bp].WholeStep ;run is at least this long - add dx,[bp].AdjUp ;advance the error term and add an extra - jnc YMajorNoExtra ; pixel if the error term so indicates - inc cx ;one extra pixel in run - sub dx,[bp].AdjDown ;reset the error term - YMajorNoExtra: - ;draw the run - YMajorRunLoop: - mov [di],al ;draw the pixel - add di,SCREEN_WIDTH ;advance along the major axis (Y) - dec cx - jnz YMajorRunLoop - add di,bx ;advance along the minor axis (X) - YMajorFullRunsOddEntry: ;enter loop here if there is an odd number - ; of full runs - mov cx,[bp].WholeStep ;run is at least this long - add dx,[bp].AdjUp ;advance the error term and add an extra - jnc YMajorNoExtra2 ; pixel if the error term so indicates - inc cx ;one extra pixel in run - sub dx,[bp].AdjDown ;reset the error term - YMajorNoExtra2: - ;draw the run - YMajorRunLoop2: - mov [di],al ;draw the pixel - add di,SCREEN_WIDTH ;advance along the major axis (Y) - dec cx - jnz YMajorRunLoop2 - add di,bx ;advance along the minor axis (X) +; 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 +; 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, + ; because of the above test) + dec cx ;both conditions met; make initial run 1 + ; shorter +YMajorAdjustDone: + mov [bp].WholeStep,ax ;whole step (minimum run length) + mov al,[bp].Color ;AL = drawing color + mov bx,[bp].XAdvance ;which way X advances +; Draw the first, partial run of pixels. +YMajorFirstLoop: + mov [di],al ;draw the pixel + add di,SCREEN_WIDTH ;advance along the major axis (Y) + dec cx + jnz YMajorFirstLoop + add di,bx ;advance along the minor axis (X) + ; Draw all full runs. + cmp si,1 ;# of full runs. Are there more than 2 + ; columns, so there are some full runs? + ; (SI = # columns - 1) + jna YMajorDrawLast ;no, no full runs + dec dx ;adjust error term by -1 so we can use + ; carry test + shr si,1 ;convert from column to column-pair count + jnc YMajorFullRunsOddEntry ;if there is an odd number of + ; columns, do the odd column now +YMajorFullRunsLoop: + mov cx,[bp].WholeStep ;run is at least this long + add dx,[bp].AdjUp ;advance the error term and add an extra + jnc YMajorNoExtra ; pixel if the error term so indicates + inc cx ;one extra pixel in run + sub dx,[bp].AdjDown ;reset the error term +YMajorNoExtra: + ;draw the run +YMajorRunLoop: + mov [di],al ;draw the pixel + add di,SCREEN_WIDTH ;advance along the major axis (Y) + dec cx + jnz YMajorRunLoop + add di,bx ;advance along the minor axis (X) +YMajorFullRunsOddEntry: ;enter loop here if there is an odd number + ; of full runs + mov cx,[bp].WholeStep ;run is at least this long + add dx,[bp].AdjUp ;advance the error term and add an extra + jnc YMajorNoExtra2 ; pixel if the error term so indicates + inc cx ;one extra pixel in run + sub dx,[bp].AdjDown ;reset the error term +YMajorNoExtra2: + ;draw the run +YMajorRunLoop2: + mov [di],al ;draw the pixel + add di,SCREEN_WIDTH ;advance along the major axis (Y) + dec cx + jnz YMajorRunLoop2 + add di,bx ;advance along the minor axis (X) - dec si - jnz YMajorFullRunsLoop - ; Draw the final run of pixels. - YMajorDrawLast: - pop cx ;get back the final run pixel length - YMajorLastLoop: - mov [di],al ;draw the pixel - add di,SCREEN_WIDTH ;advance along the major axis (Y) - dec cx - jnz YMajorLastLoop - Done: - 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 - ret - _LineDraw endp - end + dec si + jnz YMajorFullRunsLoop +; Draw the final run of pixels. +YMajorDrawLast: + pop cx ;get back the final run pixel length +YMajorLastLoop: + mov [di],al ;draw the pixel + add di,SCREEN_WIDTH ;advance along the major axis (Y) + dec cx + jnz YMajorLastLoop +Done: + 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 + ret +_LineDraw endp + end +``` diff --git a/38-02.md b/38-02.md index 0a6f087..e8821a1 100644 --- a/38-02.md +++ b/38-02.md @@ -70,258 +70,262 @@ 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 - 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, - 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 - for success, 0 if memory allocation failed. */ +```c +/* Color-fills a convex polygon. All vertices are offset by (XOffset, + 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, + 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 + for success, 0 if memory allocation failed. */ - #include - #include - #ifdef __TURBOC__ - #include - #else /* MSC */ - #include - #endif - #include "polygon.h" +#include +#include +#ifdef __TURBOC__ +#include +#else /* MSC */ +#include +#endif +#include "polygon.h" - /* Advances the index by one vertex forward through the vertex list, - wrapping at the end of the list */ - #define INDEX_FORWARD(Index) \ - Index = (Index + 1) % VertexList->Length; +/* Advances the index by one vertex forward through the vertex list, + wrapping at the end of the list */ +#define INDEX_FORWARD(Index) \ + Index = (Index + 1) % VertexList->Length; - /* Advances the index by one vertex backward through the vertex list, - wrapping at the start of the list */ - #define INDEX_BACKWARD(Index) \ - Index = (Index - 1 + VertexList->Length) % VertexList->Length; +/* Advances the index by one vertex backward through the vertex list, + wrapping at the start of the list */ +#define INDEX_BACKWARD(Index) \ + Index = (Index - 1 + VertexList->Length) % VertexList->Length; - /* Advances the index by one vertex either forward or backward through - the vertex list, wrapping at either end of the list */ - #define INDEX_MOVE(Index,Direction) \ - if (Direction > 0) \ - Index = (Index + 1) % VertexList->Length; \ - else \ - Index = (Index - 1 + VertexList->Length) % VertexList->Length; +/* Advances the index by one vertex either forward or backward through + the vertex list, wrapping at either end of the list */ +#define INDEX_MOVE(Index,Direction) \ + if (Direction > 0) \ + Index = (Index + 1) % VertexList->Length; \ + else \ + Index = (Index - 1 + VertexList->Length) % VertexList->Length; - extern void DrawHorizontalLineList(struct HLineList *, int); - static void ScanEdge(int, int, int, int, int, int, struct HLine **); +extern void DrawHorizontalLineList(struct HLineList *, int); +static void ScanEdge(int, int, int, int, int, int, struct HLine **); - int FillConvexPolygon(struct PointListHeader * VertexList, int Color, - int XOffset, int YOffset) - { - int i, MinIndexL, MaxIndex, MinIndexR, SkipFirst, Temp; - int MinPoint_Y, MaxPoint_Y, TopIsFlat, LeftEdgeDir; - int NextIndex, CurrentIndex, PreviousIndex; - int DeltaXN, DeltaYN, DeltaXP, DeltaYP; - struct HLineList WorkingHLineList; - struct HLine *EdgePointPtr; - struct Point *VertexPtr; +int FillConvexPolygon(struct PointListHeader * VertexList, int Color, + int XOffset, int YOffset) +{ + int i, MinIndexL, MaxIndex, MinIndexR, SkipFirst, Temp; + int MinPoint_Y, MaxPoint_Y, TopIsFlat, LeftEdgeDir; + int NextIndex, CurrentIndex, PreviousIndex; + int DeltaXN, DeltaYN, DeltaXP, DeltaYP; + struct HLineList WorkingHLineList; + struct HLine *EdgePointPtr; + struct Point *VertexPtr; - /* Point to the vertex list */ - VertexPtr = VertexList->PointPtr; + /* Point to the vertex list */ + VertexPtr = VertexList->PointPtr; - /* Scan the list to find the top and bottom of the polygon */ - if (VertexList->Length == 0) - return(1); /* reject null polygons */ - MaxPoint_Y = MinPoint_Y = VertexPtr[MinIndexL = MaxIndex = 0].Y; - for (i = 1; i < VertexList->Length; i++) { - if (VertexPtr[i].Y < MinPoint_Y) - MinPoint_Y = VertexPtr[MinIndexL = i].Y; /* new top */ - else if (VertexPtr[i].Y > MaxPoint_Y) - MaxPoint_Y = VertexPtr[MaxIndex = i].Y; /* new bottom */ - } - if (MinPoint_Y == MaxPoint_Y) - return(1); /* polygon is 0-height; avoid infinite loop below */ + /* Scan the list to find the top and bottom of the polygon */ + if (VertexList->Length == 0) + return(1); /* reject null polygons */ + MaxPoint_Y = MinPoint_Y = VertexPtr[MinIndexL = MaxIndex = 0].Y; + for (i = 1; i < VertexList->Length; i++) { + if (VertexPtr[i].Y < MinPoint_Y) + MinPoint_Y = VertexPtr[MinIndexL = i].Y; /* new top */ + else if (VertexPtr[i].Y > MaxPoint_Y) + MaxPoint_Y = VertexPtr[MaxIndex = i].Y; /* new bottom */ + } + if (MinPoint_Y == MaxPoint_Y) + return(1); /* polygon is 0-height; avoid infinite loop below */ - /* Scan in ascending order to find the last top-edge point */ - MinIndexR = MinIndexL; - while (VertexPtr[MinIndexR].Y == MinPoint_Y) - INDEX_FORWARD(MinIndexR); - INDEX_BACKWARD(MinIndexR); /* back up to last top-edge point */ + /* Scan in ascending order to find the last top-edge point */ + MinIndexR = MinIndexL; + while (VertexPtr[MinIndexR].Y == MinPoint_Y) + INDEX_FORWARD(MinIndexR); + INDEX_BACKWARD(MinIndexR); /* back up to last top-edge point */ - /* Now scan in descending order to find the first top-edge point */ - while (VertexPtr[MinIndexL].Y == MinPoint_Y) - INDEX_BACKWARD(MinIndexL); - INDEX_FORWARD(MinIndexL); /* back up to first top-edge point */ + /* Now scan in descending order to find the first top-edge point */ + while (VertexPtr[MinIndexL].Y == MinPoint_Y) + INDEX_BACKWARD(MinIndexL); + INDEX_FORWARD(MinIndexL); /* back up to first top-edge point */ - /* Figure out which direction through the vertex list from the top - vertex is the left edge and which is the right */ - LeftEdgeDir = -1; /* assume left edge runs down thru vertex list */ - if ((TopIsFlat = (VertexPtr[MinIndexL].X != - VertexPtr[MinIndexR].X) ? 1 : 0) == 1) { - /* If the top is flat, just see which of the ends is leftmost */ - if (VertexPtr[MinIndexL].X > VertexPtr[MinIndexR].X) { - LeftEdgeDir = 1; /* left edge runs up through vertex list */ - Temp = MinIndexL; /* swap the indices so MinIndexL */ - MinIndexL = MinIndexR; /* points to the start of the left */ - MinIndexR = Temp; /* edge, similarly for MinIndexR */ - } - } else { - /* Point to the downward end of the first line of each of the - two edges down from the top */ - NextIndex = MinIndexR; - INDEX_FORWARD(NextIndex); - PreviousIndex = MinIndexL; - INDEX_BACKWARD(PreviousIndex); - /* Calculate X and Y lengths from the top vertex to the end of - the first line down each edge; use those to compare slopes - and see which line is leftmost */ - DeltaXN = VertexPtr[NextIndex].X - VertexPtr[MinIndexL].X; - DeltaYN = VertexPtr[NextIndex].Y - VertexPtr[MinIndexL].Y; - DeltaXP = VertexPtr[PreviousIndex].X - VertexPtr[MinIndexL].X; - DeltaYP = VertexPtr[PreviousIndex].Y - VertexPtr[MinIndexL].Y; - if (((long)DeltaXN * DeltaYP - (long)DeltaYN * DeltaXP) < 0L) { - LeftEdgeDir = 1; /* left edge runs up through vertex list */ - Temp = MinIndexL; /* swap the indices so MinIndexL */ - MinIndexL = MinIndexR; /* points to the start of the left */ - MinIndexR = Temp; /* edge, similarly for MinIndexR */ - } - } - - /* 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 - 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 */ - 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 */ - - /* Scan the left edge and store the boundary points in the list */ - /* Initial pointer for storing scan converted left-edge coords */ - EdgePointPtr = WorkingHLineList.HLinePtr; - /* 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 */ - SkipFirst = TopIsFlat ? 0 : 1; - /* Scan convert each line in the left edge from top to bottom */ - do { - INDEX_MOVE(CurrentIndex,LeftEdgeDir); - ScanEdge(VertexPtr[PreviousIndex].X + XOffset, - VertexPtr[PreviousIndex].Y, - VertexPtr[CurrentIndex].X + XOffset, - VertexPtr[CurrentIndex].Y, 1, SkipFirst, &EdgePointPtr); - PreviousIndex = CurrentIndex; - SkipFirst = 0; /* scan convert the first point from now on */ - } while (CurrentIndex != MaxIndex); - - /* Scan the right edge and store the boundary points in the list */ - EdgePointPtr = WorkingHLineList.HLinePtr; - PreviousIndex = CurrentIndex = MinIndexR; - SkipFirst = TopIsFlat ? 0 : 1; - /* Scan convert the right edge, top to bottom. X coordinates are - adjusted 1 to the left, effectively causing scan conversion of - the nearest points to the left of but not exactly on the edge */ - do { - INDEX_MOVE(CurrentIndex,-LeftEdgeDir); - ScanEdge(VertexPtr[PreviousIndex].X + XOffset - 1, - VertexPtr[PreviousIndex].Y, - VertexPtr[CurrentIndex].X + XOffset - 1, - VertexPtr[CurrentIndex].Y, 0, SkipFirst, &EdgePointPtr); - PreviousIndex = CurrentIndex; - SkipFirst = 0; /* scan convert the first point from now on */ - } while (CurrentIndex != MaxIndex); - - /* Draw the line list representing the scan converted polygon */ - DrawHorizontalLineList(&WorkingHLineList, Color); - - /* Release the line list's memory and we're successfully done */ - free(WorkingHLineList.HLinePtr); - return(1); + /* Figure out which direction through the vertex list from the top + vertex is the left edge and which is the right */ + LeftEdgeDir = -1; /* assume left edge runs down thru vertex list */ + if ((TopIsFlat = (VertexPtr[MinIndexL].X != + VertexPtr[MinIndexR].X) ? 1 : 0) == 1) { + /* If the top is flat, just see which of the ends is leftmost */ + if (VertexPtr[MinIndexL].X > VertexPtr[MinIndexR].X) { + LeftEdgeDir = 1; /* left edge runs up through vertex list */ + Temp = MinIndexL; /* swap the indices so MinIndexL */ + MinIndexL = MinIndexR; /* points to the start of the left */ + MinIndexR = Temp; /* edge, similarly for MinIndexR */ } - - /* Scan converts an edge from (X1,Y1) to (X2,Y2), not including the - 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 - 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) - { - int Y, DeltaX, DeltaY; - double InverseSlope; - struct HLine *WorkingEdgePointPtr; - - /* Calculate X and Y lengths of the line and the inverse slope */ - DeltaX = X2 - X1; - if ((DeltaY = Y2 - Y1) <= 0) - return; /* guard against 0-length and horizontal edges */ - InverseSlope = (double)DeltaX / (double)DeltaY; - - /* Store the X coordinate of the pixel closest to but not to the - left of the line for each Y coordinate between Y1 and Y2, not - including Y2 and also not including Y1 if SkipFirst != 0 */ - WorkingEdgePointPtr = *EdgePointPtr; /* avoid double dereference */ - for (Y = Y1 + SkipFirst; Y < Y2; Y++, WorkingEdgePointPtr++) { - /* Store the X coordinate in the appropriate edge list */ - if (SetXStart == 1) - WorkingEdgePointPtr->XStart = - X1 + (int)(ceil((Y-Y1) * InverseSlope)); - else - WorkingEdgePointPtr->XEnd = - X1 + (int)(ceil((Y-Y1) * InverseSlope)); - } - *EdgePointPtr = WorkingEdgePointPtr; /* advance caller's ptr */ + } else { + /* Point to the downward end of the first line of each of the + two edges down from the top */ + NextIndex = MinIndexR; + INDEX_FORWARD(NextIndex); + PreviousIndex = MinIndexL; + INDEX_BACKWARD(PreviousIndex); + /* Calculate X and Y lengths from the top vertex to the end of + the first line down each edge; use those to compare slopes + and see which line is leftmost */ + DeltaXN = VertexPtr[NextIndex].X - VertexPtr[MinIndexL].X; + DeltaYN = VertexPtr[NextIndex].Y - VertexPtr[MinIndexL].Y; + DeltaXP = VertexPtr[PreviousIndex].X - VertexPtr[MinIndexL].X; + DeltaYP = VertexPtr[PreviousIndex].Y - VertexPtr[MinIndexL].Y; + if (((long)DeltaXN * DeltaYP - (long)DeltaYN * DeltaXP) < 0L) { + LeftEdgeDir = 1; /* left edge runs up through vertex list */ + Temp = MinIndexL; /* swap the indices so MinIndexL */ + MinIndexL = MinIndexR; /* points to the start of the left */ + MinIndexR = Temp; /* edge, similarly for MinIndexR */ } + } + + /* 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 + 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 */ + 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 */ + + /* Scan the left edge and store the boundary points in the list */ + /* Initial pointer for storing scan converted left-edge coords */ + EdgePointPtr = WorkingHLineList.HLinePtr; + /* 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 */ + SkipFirst = TopIsFlat ? 0 : 1; + /* Scan convert each line in the left edge from top to bottom */ + do { + INDEX_MOVE(CurrentIndex,LeftEdgeDir); + ScanEdge(VertexPtr[PreviousIndex].X + XOffset, + VertexPtr[PreviousIndex].Y, + VertexPtr[CurrentIndex].X + XOffset, + VertexPtr[CurrentIndex].Y, 1, SkipFirst, &EdgePointPtr); + PreviousIndex = CurrentIndex; + SkipFirst = 0; /* scan convert the first point from now on */ + } while (CurrentIndex != MaxIndex); + + /* Scan the right edge and store the boundary points in the list */ + EdgePointPtr = WorkingHLineList.HLinePtr; + PreviousIndex = CurrentIndex = MinIndexR; + SkipFirst = TopIsFlat ? 0 : 1; + /* Scan convert the right edge, top to bottom. X coordinates are + adjusted 1 to the left, effectively causing scan conversion of + the nearest points to the left of but not exactly on the edge */ + do { + INDEX_MOVE(CurrentIndex,-LeftEdgeDir); + ScanEdge(VertexPtr[PreviousIndex].X + XOffset - 1, + VertexPtr[PreviousIndex].Y, + VertexPtr[CurrentIndex].X + XOffset - 1, + VertexPtr[CurrentIndex].Y, 0, SkipFirst, &EdgePointPtr); + PreviousIndex = CurrentIndex; + SkipFirst = 0; /* scan convert the first point from now on */ + } while (CurrentIndex != MaxIndex); + + /* Draw the line list representing the scan converted polygon */ + DrawHorizontalLineList(&WorkingHLineList, Color); + + /* Release the line list's memory and we're successfully done */ + free(WorkingHLineList.HLinePtr); + return(1); +} + +/* Scan converts an edge from (X1,Y1) to (X2,Y2), not including the + 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 + 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) +{ + int Y, DeltaX, DeltaY; + double InverseSlope; + struct HLine *WorkingEdgePointPtr; + + /* Calculate X and Y lengths of the line and the inverse slope */ + DeltaX = X2 - X1; + if ((DeltaY = Y2 - Y1) <= 0) + return; /* guard against 0-length and horizontal edges */ + InverseSlope = (double)DeltaX / (double)DeltaY; + + /* Store the X coordinate of the pixel closest to but not to the + left of the line for each Y coordinate between Y1 and Y2, not + including Y2 and also not including Y1 if SkipFirst != 0 */ + WorkingEdgePointPtr = *EdgePointPtr; /* avoid double dereference */ + for (Y = Y1 + SkipFirst; Y < Y2; Y++, WorkingEdgePointPtr++) { + /* Store the X coordinate in the appropriate edge list */ + if (SetXStart == 1) + WorkingEdgePointPtr->XStart = + X1 + (int)(ceil((Y-Y1) * InverseSlope)); + else + WorkingEdgePointPtr->XEnd = + X1 + (int)(ceil((Y-Y1) * InverseSlope)); + } + *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- - pixel approach, which does have the virtue of being easily ported - to any environment. */ +```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- + pixel approach, which does have the virtue of being easily ported + to any environment. */ - #include - #include "polygon.h" +#include +#include "polygon.h" - #define SCREEN_WIDTH 320 - #define SCREEN_SEGMENT 0xA000 +#define SCREEN_WIDTH 320 +#define SCREEN_SEGMENT 0xA000 - static void DrawPixel(int, int, int); +static void DrawPixel(int, int, int); - void DrawHorizontalLineList(struct HLineList * HLineListPtr, - int Color) - { - struct HLine *HLinePtr; - int Y, X; +void DrawHorizontalLineList(struct HLineList * HLineListPtr, + int Color) +{ + struct HLine *HLinePtr; + int Y, X; - /* Point to the XStart/XEnd descriptor for the first (top) - horizontal line */ - HLinePtr = HLineListPtr->HLinePtr; - /* Draw each horizontal line in turn, starting with the top one and - advancing one line each time */ - for (Y = HLineListPtr->YStart; Y < (HLineListPtr->YStart + - HLineListPtr->Length); Y++, HLinePtr++) { - /* Draw each pixel in the current horizontal line in turn, - starting with the leftmost one */ - for (X = HLinePtr->XStart; X <= HLinePtr->XEnd; X++) - DrawPixel(X, Y, Color); - } - } + /* Point to the XStart/XEnd descriptor for the first (top) + horizontal line */ + HLinePtr = HLineListPtr->HLinePtr; + /* Draw each horizontal line in turn, starting with the top one and + advancing one line each time */ + for (Y = HLineListPtr->YStart; Y < (HLineListPtr->YStart + + HLineListPtr->Length); Y++, HLinePtr++) { + /* Draw each pixel in the current horizontal line in turn, + starting with the leftmost one */ + for (X = HLinePtr->XStart; X <= HLinePtr->XEnd; X++) + DrawPixel(X, Y, Color); + } +} - /* Draws the pixel at (X, Y) in color Color in VGA mode 13h */ - static void DrawPixel(int X, int Y, int Color) { - unsigned char far *ScreenPtr; +/* Draws the pixel at (X, Y) in color Color in VGA mode 13h */ +static void DrawPixel(int X, int Y, int Color) { + unsigned char far *ScreenPtr; - #ifdef __TURBOC__ - ScreenPtr = MK_FP(SCREEN_SEGMENT, Y * SCREEN_WIDTH + X); - #else /* MSC 5.0 */ - FP_SEG(ScreenPtr) = SCREEN_SEGMENT; - FP_OFF(ScreenPtr) = Y * SCREEN_WIDTH + X; - #endif - *ScreenPtr = (unsigned char)Color; - } +#ifdef __TURBOC__ + ScreenPtr = MK_FP(SCREEN_SEGMENT, Y * SCREEN_WIDTH + X); +#else /* MSC 5.0 */ + FP_SEG(ScreenPtr) = SCREEN_SEGMENT; + FP_OFF(ScreenPtr) = Y * SCREEN_WIDTH + X; +#endif + *ScreenPtr = (unsigned char)Color; +} +``` diff --git a/38-03.md b/38-03.md index fa84d4d..a2b2822 100644 --- a/38-03.md +++ b/38-03.md @@ -12,130 +12,134 @@ pages: 717-720 **LISTING 38.3 L38-3.C** - /* Sample program to exercise the polygon-filling routines. This code - and all polygon-filling code has been tested with Borland and - Microsoft compilers. */ +```c +/* Sample program to exercise the polygon-filling routines. This code + and all polygon-filling code has been tested with Borland and + Microsoft compilers. */ - #include - #include - #include "polygon.h" +#include +#include +#include "polygon.h" - /* Draws the polygon described by the point list PointList in color - Color with all vertices offset by (X,Y) */ - #define DRAW_POLYGON(PointList,Color,X,Y) \ - Polygon.Length = sizeof(PointList)/sizeof(struct Point); \ - Polygon.PointPtr = PointList; \ - FillConvexPolygon(&Polygon, Color, X, Y); - - void main(void); - extern int FillConvexPolygon(struct PointListHeader *, int, int, int); +/* Draws the polygon described by the point list PointList in color + Color with all vertices offset by (X,Y) */ +#define DRAW_POLYGON(PointList,Color,X,Y) \ + Polygon.Length = sizeof(PointList)/sizeof(struct Point); \ + Polygon.PointPtr = PointList; \ + FillConvexPolygon(&Polygon, Color, X, Y); + +void main(void); +extern int FillConvexPolygon(struct PointListHeader *, int, int, int); - void main() { - int i, j; - struct PointListHeader Polygon; - static struct Point ScreenRectangle[] = - {{0,0},{320,0},{320,200},{0,200}}; - static struct Point ConvexShape[] = - {{0,0},{121,0},{320,0},{200,51},{301,51},{250,51},{319,143}, - {320,200},{22,200},{0,200},{50,180},{20,160},{50,140}, - {20,120},{50,100},{20,80},{50,60},{20,40},{50,20}}; - static struct Point Hexagon[] = - {{90,-50},{0,-90},{-90,-50},{-90,50},{0,90},{90,50}}; - static struct Point Triangle1[] = {{30,0},{15,20},{0,0}}; - static struct Point Triangle2[] = {{30,20},{15,0},{0,20}}; - static struct Point Triangle3[] = {{0,20},{20,10},{0,0}}; - static struct Point Triangle4[] = {{20,20},{20,0},{0,10}}; - union REGS regset; +void main() { + int i, j; + struct PointListHeader Polygon; + static struct Point ScreenRectangle[] = + {{0,0},{320,0},{320,200},{0,200}}; + static struct Point ConvexShape[] = + {{0,0},{121,0},{320,0},{200,51},{301,51},{250,51},{319,143}, + {320,200},{22,200},{0,200},{50,180},{20,160},{50,140}, + {20,120},{50,100},{20,80},{50,60},{20,40},{50,20}}; + static struct Point Hexagon[] = + {{90,-50},{0,-90},{-90,-50},{-90,50},{0,90},{90,50}}; + static struct Point Triangle1[] = {{30,0},{15,20},{0,0}}; + static struct Point Triangle2[] = {{30,20},{15,0},{0,20}}; + static struct Point Triangle3[] = {{0,20},{20,10},{0,0}}; + static struct Point Triangle4[] = {{20,20},{20,0},{0,10}}; + union REGS regset; - /* Set the display to VGA mode 13h, 320x200 256-color mode */ - regset.x.ax = 0x0013; /* AH = 0 selects mode set function, - AL = 0x13 selects mode 0x13 - when set as parameters for INT 0x10 */ - int86(0x10, ®set, ®set); + /* Set the display to VGA mode 13h, 320x200 256-color mode */ + regset.x.ax = 0x0013; /* AH = 0 selects mode set function, + AL = 0x13 selects mode 0x13 + when set as parameters for INT 0x10 */ + int86(0x10, ®set, ®set); - /* Clear the screen to cyan */ - DRAW_POLYGON(ScreenRectangle, 3, 0, 0); + /* Clear the screen to cyan */ + DRAW_POLYGON(ScreenRectangle, 3, 0, 0); - /* Draw an irregular shape that meets our definition of convex but - is not convex by any normal description */ - DRAW_POLYGON(ConvexShape, 6, 0, 0); - getch(); /* wait for a keypress */ + /* Draw an irregular shape that meets our definition of convex but + is not convex by any normal description */ + DRAW_POLYGON(ConvexShape, 6, 0, 0); + getch(); /* wait for a keypress */ - /* Draw adjacent triangles across the top half of the screen */ - for (j=0; j<=80; j+=20) { - for (i=0; i<290; i += 30) { - DRAW_POLYGON(Triangle1, 2, i, j); - DRAW_POLYGON(Triangle2, 4, i+15, j); - } - } - - /* Draw adjacent triangles across the bottom half of the screen */ - for (j=100; j<=170; j+=20) { - /* Do a row of pointing-right triangles */ - for (i=0; i<290; i += 20) { - DRAW_POLYGON(Triangle3, 40, i, j); - } - /* Do a row of pointing-left triangles halfway between one row - of pointing-right triangles and the next, to fit between */ - for (i=0; i<290; i += 20) { - DRAW_POLYGON(Triangle4, 1, i, j+10); - } - } - getch(); /* wait for a keypress */ - - /* Finally, draw a series of concentric hexagons of approximately - the same proportions in the center of the screen */ - for (i=0; i<16; i++) { - DRAW_POLYGON(Hexagon, i, 160, 100); - for (j=0; j= 0 ? 3 : -3; - Hexagon[j].Y -= Hexagon[j].Y >= 0 ? 2 : -2; - } else { - Hexagon[j].Y -= Hexagon[j].Y >= 0 ? 3 : -3; - } - } - } - getch(); /* wait for a keypress */ - - /* Return to text mode and exit */ - regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */ - int86(0x10, ®set, ®set); + /* Draw adjacent triangles across the top half of the screen */ + for (j=0; j<=80; j+=20) { + for (i=0; i<290; i += 30) { + DRAW_POLYGON(Triangle1, 2, i, j); + DRAW_POLYGON(Triangle2, 4, i+15, j); } + } + + /* Draw adjacent triangles across the bottom half of the screen */ + for (j=100; j<=170; j+=20) { + /* Do a row of pointing-right triangles */ + for (i=0; i<290; i += 20) { + DRAW_POLYGON(Triangle3, 40, i, j); + } + /* Do a row of pointing-left triangles halfway between one row + of pointing-right triangles and the next, to fit between */ + for (i=0; i<290; i += 20) { + DRAW_POLYGON(Triangle4, 1, i, j+10); + } + } + getch(); /* wait for a keypress */ + + /* Finally, draw a series of concentric hexagons of approximately + the same proportions in the center of the screen */ + for (i=0; i<16; i++) { + DRAW_POLYGON(Hexagon, i, 160, 100); + for (j=0; j= 0 ? 3 : -3; + Hexagon[j].Y -= Hexagon[j].Y >= 0 ? 2 : -2; + } else { + Hexagon[j].Y -= Hexagon[j].Y >= 0 ? 3 : -3; + } + } + } + getch(); /* wait for a keypress */ + + /* Return to text mode and exit */ + regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */ + int86(0x10, ®set, ®set); +} +``` **LISTING 38.4 POLYGON.H** - /* POLYGON.H: Header file for polygon-filling code */ +```c +/* POLYGON.H: Header file for polygon-filling code */ - /* Describes a single point (used for a single vertex) */ - struct Point { - int X; /* X coordinate */ - int Y; /* Y coordinate */ - }; +/* Describes a single point (used for a single vertex) */ +struct Point { + int X; /* X coordinate */ + int Y; /* Y coordinate */ +}; - /* Describes a series of points (used to store a list of vertices that - describe a polygon; each vertex is assumed to connect to the two - adjacent vertices, and the last vertex is assumed to connect to the - first) */ - struct PointListHeader { - int Length; /* # of points */ - struct Point * PointPtr; /* pointer to list of points */ - }; +/* Describes a series of points (used to store a list of vertices that + describe a polygon; each vertex is assumed to connect to the two + adjacent vertices, and the last vertex is assumed to connect to the + first) */ +struct PointListHeader { + int Length; /* # of points */ + struct Point * PointPtr; /* pointer to list of points */ +}; - /* Describes the beginning and ending X coordinates of a single - horizontal line */ - struct HLine { - int XStart; /* X coordinate of leftmost pixel in line */ - int XEnd; /* X coordinate of rightmost pixel in line */ - }; +/* Describes the beginning and ending X coordinates of a single + horizontal line */ +struct HLine { + int XStart; /* X coordinate of leftmost pixel in line */ + int XEnd; /* X coordinate of rightmost pixel in line */ +}; - /* Describes a Length-long series of horizontal lines, all assumed to - be on contiguous scan lines starting at YStart and proceeding - downward (used to describe a scan-converted polygon to the - low-level hardware-dependent drawing code) */ - struct HLineList { - int Length; /* # of horizontal lines */ - int YStart; /* Y coordinate of topmost line */ - struct HLine * HLinePtr; /* pointer to list of horz lines */ - }; +/* Describes a Length-long series of horizontal lines, all assumed to + be on contiguous scan lines starting at YStart and proceeding + downward (used to describe a scan-converted polygon to the + low-level hardware-dependent drawing code) */ +struct HLineList { + int Length; /* # of horizontal lines */ + int YStart; /* Y coordinate of topmost line */ + struct HLine * HLinePtr; /* pointer to list of horz lines */ +}; +``` diff --git a/39-02.md b/39-02.md index e076457..ab73213 100644 --- a/39-02.md +++ b/39-02.md @@ -45,45 +45,47 @@ equal 100. **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 - 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. - All C code tested with Borland C++. */ +```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 + 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. + All C code tested with Borland C++. */ - #include - #include - #include "polygon.h" +#include +#include +#include "polygon.h" - #define SCREEN_WIDTH 320 - #define SCREEN_SEGMENT 0xA000 +#define SCREEN_WIDTH 320 +#define SCREEN_SEGMENT 0xA000 - void DrawHorizontalLineList(struct HLineList * HLineListPtr, - int Color) - { - struct HLine *HLinePtr; - int Length, Width; - unsigned char far *ScreenPtr; +void DrawHorizontalLineList(struct HLineList * HLineListPtr, + int Color) +{ + struct HLine *HLinePtr; + int Length, Width; + unsigned char far *ScreenPtr; - /* Point to the start of the first scan line on which to draw */ - ScreenPtr = MK_FP(SCREEN_SEGMENT, - HLineListPtr->YStart * SCREEN_WIDTH); + /* Point to the start of the first scan line on which to draw */ + ScreenPtr = MK_FP(SCREEN_SEGMENT, + HLineListPtr->YStart * SCREEN_WIDTH); - /* Point to the XStart/XEnd descriptor for the first (top) - horizontal line */ - HLinePtr = HLineListPtr->HLinePtr; - /* Draw each horizontal line in turn, starting with the top one and - advancing one line each time */ - Length = HLineListPtr->Length; - while (Length-- > 0) { - /* Draw the whole horizontal line if it has a positive width */ - if ((Width = HLinePtr->XEnd - HLinePtr->XStart + 1) > 0) - memset(ScreenPtr + HLinePtr->XStart, Color, Width); - HLinePtr++; /* point to next scan line X info */ - ScreenPtr += SCREEN_WIDTH; /* point to next scan line start */ - } - } + /* Point to the XStart/XEnd descriptor for the first (top) + horizontal line */ + HLinePtr = HLineListPtr->HLinePtr; + /* Draw each horizontal line in turn, starting with the top one and + advancing one line each time */ + Length = HLineListPtr->Length; + while (Length-- > 0) { + /* Draw the whole horizontal line if it has a positive width */ + if ((Width = HLinePtr->XEnd - HLinePtr->XStart + 1) > 0) + memset(ScreenPtr + HLinePtr->XStart, Color, Width); + HLinePtr++; /* point to next scan line X info */ + ScreenPtr += SCREEN_WIDTH; /* point to next scan line start */ + } +} +``` 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 diff --git a/39-03.md b/39-03.md index bb13dd3..b0e38d5 100644 --- a/39-03.md +++ b/39-03.md @@ -26,121 +26,123 @@ 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 - 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. */ +```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 + 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 +#include "polygon.h" - void ScanEdge(int X1, int Y1, int X2, int Y2, int SetXStart, - int SkipFirst, struct HLine **EdgePointPtr) - { - int Y, DeltaX, Height, Width, AdvanceAmt, ErrorTerm, i; - int ErrorTermAdvance, XMajorAdvanceAmt; - struct HLine *WorkingEdgePointPtr; +void ScanEdge(int X1, int Y1, int X2, int Y2, int SetXStart, + int SkipFirst, struct HLine **EdgePointPtr) +{ + int Y, DeltaX, Height, Width, AdvanceAmt, ErrorTerm, i; + int ErrorTermAdvance, XMajorAdvanceAmt; + struct HLine *WorkingEdgePointPtr; - WorkingEdgePointPtr = *EdgePointPtr; /* avoid double dereference */ - AdvanceAmt = ((DeltaX = X2 - X1) > 0) ? 1 : -1; - /* direction in which X moves (Y2 is - always > Y1, so Y always counts up) */ + WorkingEdgePointPtr = *EdgePointPtr; /* avoid double dereference */ + AdvanceAmt = ((DeltaX = X2 - X1) > 0) ? 1 : -1; + /* direction in which X moves (Y2 is + always > Y1, so Y always counts up) */ - if ((Height = Y2 - Y1) <= 0) /* Y length of the edge */ - return; /* guard against 0-length and horizontal edges */ + if ((Height = Y2 - Y1) <= 0) /* Y length of the edge */ + return; /* guard against 0-length and horizontal edges */ - /* Figure out whether the edge is vertical, diagonal, X-major - (mostly horizontal), or Y-major (mostly vertical) and handle - appropriately */ - if ((Width = abs(DeltaX)) == 0) { - /* The edge is vertical; special-case by just storing the same - X coordinate for every scan line */ - /* Scan the edge for each scan line in turn */ - for (i = Height - SkipFirst; i-- > 0; WorkingEdgePointPtr++) { - /* Store the X coordinate in the appropriate edge list */ - if (SetXStart == 1) - WorkingEdgePointPtr->XStart = X1; - else - WorkingEdgePointPtr->XEnd = X1; - } - } else if (Width == Height) { - /* The edge is diagonal; special-case by advancing the X - coordinate 1 pixel for each scan line */ - if (SkipFirst) /* skip the first point if so indicated */ - X1 += AdvanceAmt; /* move 1 pixel to the left or right */ - /* Scan the edge for each scan line in turn */ - for (i = Height - SkipFirst; i-- > 0; WorkingEdgePointPtr++) { - /* Store the X coordinate in the appropriate edge list */ - if (SetXStart == 1) - WorkingEdgePointPtr->XStart = X1; - else - WorkingEdgePointPtr->XEnd = X1; - X1 += AdvanceAmt; /* move 1 pixel to the left or right */ - } - } else if (Height > Width) { - /* Edge is closer to vertical than horizontal (Y-major) */ - if (DeltaX >= 0) - ErrorTerm = 0; /* initial error term going left->right */ - 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 */ - if ((ErrorTerm += Width) > 0) { - X1 += AdvanceAmt; /* move 1 pixel to the left or right */ - ErrorTerm -= Height; /* advance ErrorTerm to next point */ - } - } - /* Scan the edge for each scan line in turn */ - for (i = Height - SkipFirst; i-- > 0; WorkingEdgePointPtr++) { - /* Store the X coordinate in the appropriate edge list */ - if (SetXStart == 1) - WorkingEdgePointPtr->XStart = X1; - else - WorkingEdgePointPtr->XEnd = X1; - /* 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 */ - } - } - } else { - /* Edge is closer to horizontal than vertical (X-major) */ - /* Minimum distance to advance X each time */ - XMajorAdvanceAmt = (Width / Height) * AdvanceAmt; - /* Error term advance for deciding when to advance X 1 extra */ - ErrorTermAdvance = Width % Height; - if (DeltaX >= 0) - ErrorTerm = 0; /* initial error term going left->right */ - else - 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 */ - if ((ErrorTerm += ErrorTermAdvance) > 0) { - X1 += AdvanceAmt; /* move X one more */ - ErrorTerm -= Height; /* advance ErrorTerm to correspond */ - } - } - /* Scan the edge for each scan line in turn */ - for (i = Height - SkipFirst; i-- > 0; WorkingEdgePointPtr++) { - /* Store the X coordinate in the appropriate edge list */ - if (SetXStart == 1) - WorkingEdgePointPtr->XStart = X1; - else - WorkingEdgePointPtr->XEnd = X1; - X1 += XMajorAdvanceAmt; /* move X minimum distance */ - /* 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 */ - } - } - } + /* Figure out whether the edge is vertical, diagonal, X-major + (mostly horizontal), or Y-major (mostly vertical) and handle + appropriately */ + if ((Width = abs(DeltaX)) == 0) { + /* The edge is vertical; special-case by just storing the same + X coordinate for every scan line */ + /* Scan the edge for each scan line in turn */ + for (i = Height - SkipFirst; i-- > 0; WorkingEdgePointPtr++) { + /* Store the X coordinate in the appropriate edge list */ + if (SetXStart == 1) + WorkingEdgePointPtr->XStart = X1; + else + WorkingEdgePointPtr->XEnd = X1; + } + } else if (Width == Height) { + /* The edge is diagonal; special-case by advancing the X + coordinate 1 pixel for each scan line */ + if (SkipFirst) /* skip the first point if so indicated */ + X1 += AdvanceAmt; /* move 1 pixel to the left or right */ + /* Scan the edge for each scan line in turn */ + for (i = Height - SkipFirst; i-- > 0; WorkingEdgePointPtr++) { + /* Store the X coordinate in the appropriate edge list */ + if (SetXStart == 1) + WorkingEdgePointPtr->XStart = X1; + else + WorkingEdgePointPtr->XEnd = X1; + X1 += AdvanceAmt; /* move 1 pixel to the left or right */ + } + } else if (Height > Width) { + /* Edge is closer to vertical than horizontal (Y-major) */ + if (DeltaX >= 0) + ErrorTerm = 0; /* initial error term going left->right */ + 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 */ + if ((ErrorTerm += Width) > 0) { + X1 += AdvanceAmt; /* move 1 pixel to the left or right */ + ErrorTerm -= Height; /* advance ErrorTerm to next point */ + } + } + /* Scan the edge for each scan line in turn */ + for (i = Height - SkipFirst; i-- > 0; WorkingEdgePointPtr++) { + /* Store the X coordinate in the appropriate edge list */ + if (SetXStart == 1) + WorkingEdgePointPtr->XStart = X1; + else + WorkingEdgePointPtr->XEnd = X1; + /* 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 */ + } + } + } else { + /* Edge is closer to horizontal than vertical (X-major) */ + /* Minimum distance to advance X each time */ + XMajorAdvanceAmt = (Width / Height) * AdvanceAmt; + /* Error term advance for deciding when to advance X 1 extra */ + ErrorTermAdvance = Width % Height; + if (DeltaX >= 0) + ErrorTerm = 0; /* initial error term going left->right */ + else + 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 */ + if ((ErrorTerm += ErrorTermAdvance) > 0) { + X1 += AdvanceAmt; /* move X one more */ + ErrorTerm -= Height; /* advance ErrorTerm to correspond */ + } + } + /* Scan the edge for each scan line in turn */ + for (i = Height - SkipFirst; i-- > 0; WorkingEdgePointPtr++) { + /* Store the X coordinate in the appropriate edge list */ + if (SetXStart == 1) + WorkingEdgePointPtr->XStart = X1; + else + WorkingEdgePointPtr->XEnd = X1; + X1 += XMajorAdvanceAmt; /* move X minimum distance */ + /* 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 */ + } + } + } - *EdgePointPtr = WorkingEdgePointPtr; /* advance caller's ptr */ - } + *EdgePointPtr = WorkingEdgePointPtr; /* advance caller's ptr */ +} +``` ### The Finishing Touch: Assembly Language {#Heading6} diff --git a/39-04.md b/39-04.md index a3867e8..6f08808 100644 --- a/39-04.md +++ b/39-04.md @@ -33,91 +33,93 @@ as the C code. **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 - ; each line. - ; C near-callable as: - ; void DrawHorizontalLineList(struct HLineList * HLineListPtr, - ; int Color); - ; All assembly code tested with TASM and MASM +```nasm +; 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 +; each line. +; C near-callable as: +; void DrawHorizontalLineList(struct HLineList * HLineListPtr, +; int Color); +; All assembly code tested with TASM and MASM - SCREEN_WIDTH equ 320 - SCREEN_SEGMENT equ 0a000h +SCREEN_WIDTH equ 320 +SCREEN_SEGMENT equ 0a000h - HLinestruc - XStart dw ? ;X coordinate of leftmost pixel in line - XEnd dw ? ;X coordinate of rightmost pixel in line - HLine ends +HLinestruc +XStart dw ? ;X coordinate of leftmost pixel in line +XEnd dw ? ;X coordinate of rightmost pixel in line +HLine ends - HLineList struc - Lngth dw ? ;# of horizontal lines - YStart dw ? ;Y coordinate of topmost line - HLinePtr dw ? ;pointer to list of horz lines - HLineList ends +HLineList struc +Lngth dw ? ;# of horizontal lines +YStart dw ? ;Y coordinate of topmost line +HLinePtr dw ? ;pointer to list of horz lines +HLineList ends - Parms struc - dw 2 dup(?) ;return address & pushed BP - HLineListPtr dw ? ;pointer to HLineList structure - Color dw ? ;color with which to fill - Parms ends +Parms struc + dw 2 dup(?) ;return address & pushed BP +HLineListPtr dw ? ;pointer to HLineList structure +Color dw ? ;color with which to fill +Parms ends - .model small - .code - public _DrawHorizontalLineList - align 2 - _DrawHorizontalLineList proc - push bp ;preserve caller's stack frame - mov bp,sp ;point to our stack frame - push si ;preserve caller's register variables - push di - cld ;make string instructions inc pointers + .model small + .code + public _DrawHorizontalLineList + align 2 +_DrawHorizontalLineList proc + push bp ;preserve caller's stack frame + mov bp,sp ;point to our stack frame + push si ;preserve caller's register variables + push di + cld ;make string instructions inc pointers - mov ax,SCREEN_SEGMENT - mov es,ax ;point ES to display memory for REP STOS + mov ax,SCREEN_SEGMENT + mov es,ax ;point ES to display memory for REP STOS - mov si,[bp+HLineListPtr] ;point to the line list - mov ax,SCREEN_WIDTH ;point to the start of the first scan - mul [si+YStart] ; line in which to draw - mov dx,ax ;ES:DX points to first scan line to - ; draw - mov bx,[si+HLinePtr] ;point to the XStart/XEnd descriptor - ; 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 - 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 - sub cx,di - js LineFillDone ;skip if negative width - inc cx ;width of fill on this line - add di,dx ;offset of left edge of fill - test di,1 ;does fill start at an odd address? - jz MainFill ;no - stosb ;yes, draw the odd leading byte to - ; word-align the rest of the fill - dec cx ;count off the odd leading byte - jz LineFillDone ;done if that was the only byte - 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 - ; do, 0 otherwise - rep stosb ;fill any odd trailing byte - LineFillDone: - add bx,size HLine ;point to the next line descriptor - add dx,SCREEN_WIDTH ;point to the next scan line - dec si ;count off lines to fill - jnz FillLoop - FillDone: - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - _DrawHorizontalLineList endp - end + mov si,[bp+HLineListPtr] ;point to the line list + mov ax,SCREEN_WIDTH ;point to the start of the first scan + mul [si+YStart] ; line in which to draw + mov dx,ax ;ES:DX points to first scan line to + ; draw + mov bx,[si+HLinePtr] ;point to the XStart/XEnd descriptor + ; 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 + 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 + sub cx,di + js LineFillDone ;skip if negative width + inc cx ;width of fill on this line + add di,dx ;offset of left edge of fill + test di,1 ;does fill start at an odd address? + jz MainFill ;no + stosb ;yes, draw the odd leading byte to + ; word-align the rest of the fill + dec cx ;count off the odd leading byte + jz LineFillDone ;done if that was the only byte +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 + ; do, 0 otherwise + rep stosb ;fill any odd trailing byte +LineFillDone: + add bx,size HLine ;point to the next line descriptor + add dx,SCREEN_WIDTH ;point to the next scan line + dec si ;count off lines to fill + jnz FillLoop +FillDone: + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame + ret +_DrawHorizontalLineList endp + end +``` #### Maximizing REP STOS {#Heading7} diff --git a/39-05.md b/39-05.md index 5121106..3092f29 100644 --- a/39-05.md +++ b/39-05.md @@ -12,169 +12,171 @@ pages: 735-738 **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 - ; 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. - ; C near-callable as: - ; void ScanEdge(int X1, int Y1, int X2, int Y2, int SetXStart, - ; int SkipFirst, struct HLine **EdgePointPtr); - ; Edges must not go bottom to top; that is, Y1 must be <= Y2. - ; Updates the pointer pointed to by EdgePointPtr to point to the next - ; free entry in the array of HLine structures. +```nasm +; 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 +; 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. +; C near-callable as: +; void ScanEdge(int X1, int Y1, int X2, int Y2, int SetXStart, +; int SkipFirst, struct HLine **EdgePointPtr); +; Edges must not go bottom to top; that is, Y1 must be <= Y2. +; Updates the pointer pointed to by EdgePointPtr to point to the next +; free entry in the array of HLine structures. - HLine struc - XStart dw ? ;X coordinate of leftmost pixel in scan line - XEnd dw ? ;X coordinate of rightmost pixel in scan line - HLine ends +HLine struc +XStart dw ? ;X coordinate of leftmost pixel in scan line +XEnd dw ? ;X coordinate of rightmost pixel in scan line +HLine ends - Parms struc - dw 2 dup(?) ;return address & pushed BP - X1 dw ? ;X start coord of edge - Y1 dw ? ;Y start coord of edge - X2 dw ? ;X end coord of edge - Y2 dw ? ;Y end coord of edge - SetXStart dw ? ;1 to set the XStart field of each - ; HLine struc, 0 to set XEnd - SkipFirst dw ? ;1 to skip scanning the first point - ; of the edge, 0 to scan first point - EdgePointPtr dw ? ;pointer to a pointer to the array of - ; HLine structures in which to store - ; the scanned X coordinates - Parms ends +Parms struc + dw 2 dup(?) ;return address & pushed BP +X1 dw ? ;X start coord of edge +Y1 dw ? ;Y start coord of edge +X2 dw ? ;X end coord of edge +Y2 dw ? ;Y end coord of edge +SetXStart dw ? ;1 to set the XStart field of each + ; HLine struc, 0 to set XEnd +SkipFirst dw ? ;1 to skip scanning the first point + ; of the edge, 0 to scan first point +EdgePointPtr dw ? ;pointer to a pointer to the array of + ; HLine structures in which to store + ; the scanned X coordinates +Parms ends - ;Offsets from BP in stack frame of local variables. - AdvanceAmt equ -2 - Height equ -4 - LOCAL_SIZE equ 4 ;total size of local variables +;Offsets from BP in stack frame of local variables. +AdvanceAmt equ -2 +Height equ -4 +LOCAL_SIZE equ 4 ;total size of local variables - .model small - .code - public _ScanEdge - align 2 - _ScanEdge proc - 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 di - mov di,[bp+EdgePointPtr] - mov di,[di] ;point to the HLine array - cmp [bp+SetXStart],1 ;set the XStart field of each HLine - ; struc? - jz HLinePtrSet ;yes, DI points to the first XStart - add di,XEnd ;no, point to the XEnd field of the - ; first HLine struc - HLinePtrSet: - mov bx,[bp+Y2] - sub bx,[bp+Y1] ;edge height - 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) - 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 - jns SetAdvanceAmt ;DeltaX >= 0 - mov cx,1 ;DeltaX < 0 (move left as we draw) - sub cx,bx ;ErrorTerm = -Height + 1 - neg dx ;AdvanceAmt = -1 (move left) - neg ax ;Width = abs(DeltaX) - SetAdvanceAmt: - 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 - sub dx,dx ;prepare DX:AX (Width) for division - div bx ;Width/Height - ;DX = error term advance per scan line - mov si,ax ;SI = minimum # of pixels to advance X - ; on each scan line - test [bp+AdvanceAmt],8000h ;move left or right? - jz XMajorAdvanceAmtSet ;right, already set - neg si ;left, negate the distance to advance - ; on each scan line - XMajorAdvanceAmtSet: ; - mov ax,[bp+X1] ;starting X coordinate - cmp [bp+SkipFirst],1 ;skip the first point? - jz XMajorSkipEntry ;yes - XMajorLoop: - mov [di],ax ;store the current X value - add di,size HLine ;point to the next HLine struc - XMajorSkipEntry: - add ax,si ;set X for the next scan line - add cx,dx ;advance error term - jle XMajorNoAdvance ;not time for X coord to advance one - ; extra - add ax,[bp+AdvanceAmt] ;advance X coord one extra - sub cx,[bp+Height] ;adjust error term back - XMajorNoAdvance: - dec bx ;count off this scan line - jnz XMajorLoop - jmp ScanEdgeDone - align 2 - ToScanEdgeExit: - jmp ScanEdgeExit - align2 - IsVertical: - mov ax,[bp+X1] ;starting (and only) X coordinate - sub bx,[bp+SkipFirst] ;loop count = Height - SkipFirst - jz ScanEdgeExit ;no scan lines left after skipping 1st - VerticalLoop: - mov [di],ax ;store the current X value - add di,size HLine ;point to the next HLine struc - dec bx ;count off this scan line - jnz VerticalLoop - jmp ScanEdgeDone - align 2 - IsDiagonal: - mov ax,[bp+X1] ;starting X coordinate - cmp [bp+SkipFirst],1 ;skip the first point? - jz DiagonalSkipEntry ;yes - DiagonalLoop: - mov [di],ax ;store the current X value - add di,size HLine ;point to the next HLine struc - DiagonalSkipEntry: - add ax,dx ;advance the X coordinate - dec bx ;count off this scan line - jnz DiagonalLoop - jmp ScanEdgeDone - align 2 - YMajor: - pushbp ;preserve stack frame pointer - mov si,[bp+X1] ;starting X coordinate - cmp [bp+SkipFirst],1 ;skip the first point? - mov bp,bx ;put Height in BP for error term calcs - jz YMajorSkipEntry ;yes, skip the first point - YMajorLoop: - mov [di],si ;store the current X value - add di,size HLine ;point to the next HLine struc - YMajorSkipEntry: - add cx,ax ;advance the error term - jle YMajorNoAdvance ;not time for X coord to advance - add si,dx ;advance the X coordinate - sub cx,bp ;adjust error term back - YMajorNoAdvance: - dec bx ;count off this scan line - jnz YMajorLoop - popbp ;restore stack frame pointer - ScanEdgeDone: - cmp [bp+SetXStart],1 ;were we working with XStart field? - jz UpdateHLinePtr ;yes, DI points to the next XStart - 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 - ScanEdgeExit: - pop di ;restore caller's register variables - pop si - mov sp,bp ;deallocate local variables - pop bp ;restore caller's stack frame - ret - _ScanEdge endp - end + .model small + .code + public _ScanEdge + align 2 +_ScanEdge proc + 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 di + mov di,[bp+EdgePointPtr] + mov di,[di] ;point to the HLine array + cmp [bp+SetXStart],1 ;set the XStart field of each HLine + ; struc? + jz HLinePtrSet ;yes, DI points to the first XStart + add di,XEnd ;no, point to the XEnd field of the + ; first HLine struc +HLinePtrSet: + mov bx,[bp+Y2] + sub bx,[bp+Y1] ;edge height + 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) + 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 + jns SetAdvanceAmt ;DeltaX >= 0 + mov cx,1 ;DeltaX < 0 (move left as we draw) + sub cx,bx ;ErrorTerm = -Height + 1 + neg dx ;AdvanceAmt = -1 (move left) + neg ax ;Width = abs(DeltaX) +SetAdvanceAmt: + 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 + sub dx,dx ;prepare DX:AX (Width) for division + div bx ;Width/Height + ;DX = error term advance per scan line + mov si,ax ;SI = minimum # of pixels to advance X + ; on each scan line + test [bp+AdvanceAmt],8000h ;move left or right? + jz XMajorAdvanceAmtSet ;right, already set + neg si ;left, negate the distance to advance + ; on each scan line +XMajorAdvanceAmtSet: ; + mov ax,[bp+X1] ;starting X coordinate + cmp [bp+SkipFirst],1 ;skip the first point? + jz XMajorSkipEntry ;yes +XMajorLoop: + mov [di],ax ;store the current X value + add di,size HLine ;point to the next HLine struc +XMajorSkipEntry: + add ax,si ;set X for the next scan line + add cx,dx ;advance error term + jle XMajorNoAdvance ;not time for X coord to advance one + ; extra + add ax,[bp+AdvanceAmt] ;advance X coord one extra + sub cx,[bp+Height] ;adjust error term back +XMajorNoAdvance: + dec bx ;count off this scan line + jnz XMajorLoop + jmp ScanEdgeDone + align 2 +ToScanEdgeExit: + jmp ScanEdgeExit + align2 +IsVertical: + mov ax,[bp+X1] ;starting (and only) X coordinate + sub bx,[bp+SkipFirst] ;loop count = Height - SkipFirst + jz ScanEdgeExit ;no scan lines left after skipping 1st +VerticalLoop: + mov [di],ax ;store the current X value + add di,size HLine ;point to the next HLine struc + dec bx ;count off this scan line + jnz VerticalLoop + jmp ScanEdgeDone + align 2 +IsDiagonal: + mov ax,[bp+X1] ;starting X coordinate + cmp [bp+SkipFirst],1 ;skip the first point? + jz DiagonalSkipEntry ;yes +DiagonalLoop: + mov [di],ax ;store the current X value + add di,size HLine ;point to the next HLine struc +DiagonalSkipEntry: + add ax,dx ;advance the X coordinate + dec bx ;count off this scan line + jnz DiagonalLoop + jmp ScanEdgeDone + align 2 +YMajor: + pushbp ;preserve stack frame pointer + mov si,[bp+X1] ;starting X coordinate + cmp [bp+SkipFirst],1 ;skip the first point? + mov bp,bx ;put Height in BP for error term calcs + jz YMajorSkipEntry ;yes, skip the first point +YMajorLoop: + mov [di],si ;store the current X value + add di,size HLine ;point to the next HLine struc +YMajorSkipEntry: + add cx,ax ;advance the error term + jle YMajorNoAdvance ;not time for X coord to advance + add si,dx ;advance the X coordinate + sub cx,bp ;adjust error term back +YMajorNoAdvance: + dec bx ;count off this scan line + jnz YMajorLoop + popbp ;restore stack frame pointer +ScanEdgeDone: + cmp [bp+SetXStart],1 ;were we working with XStart field? + jz UpdateHLinePtr ;yes, DI points to the next XStart + 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 +ScanEdgeExit: + pop di ;restore caller's register variables + pop si + mov sp,bp ;deallocate local variables + pop bp ;restore caller's stack frame + ret +_ScanEdge endp + end +``` diff --git a/40-02.md b/40-02.md index 79c240e..fc87b49 100644 --- a/40-02.md +++ b/40-02.md @@ -70,280 +70,282 @@ more sane and sensible corners of the universe. **LISTING 40.1 L40-1.C** - /* Color-fills an arbitrarily-shaped polygon described by VertexList. - If the first and last points in VertexList are not the same, the path - around the polygon is automatically closed. All vertices are offset - 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 - 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, - 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. - Define CONVEX_CODE_LINKED if the fast convex polygon filling code from - Chapter 38 is linked in. Otherwise, convex polygons are - handled by the complex polygon filling code. - Nonconvex is handled as complex in this implementation. See text for a - discussion of faster nonconvex handling. */ +```c +/* Color-fills an arbitrarily-shaped polygon described by VertexList. + If the first and last points in VertexList are not the same, the path + around the polygon is automatically closed. All vertices are offset + 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 + 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, + 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. + Define CONVEX_CODE_LINKED if the fast convex polygon filling code from + Chapter 38 is linked in. Otherwise, convex polygons are + handled by the complex polygon filling code. + Nonconvex is handled as complex in this implementation. See text for a + discussion of faster nonconvex handling. */ - #include - #include - #ifdef __TURBOC__ - #include - #else /* MSC */ - #include - #endif - #include "polygon.h" +#include +#include +#ifdef __TURBOC__ +#include +#else /* MSC */ +#include +#endif +#include "polygon.h" - #define SWAP(a,b) {temp = a; a = b; b = temp;} +#define SWAP(a,b) {temp = a; a = b; b = temp;} - struct EdgeState { - struct EdgeState *NextEdge; - int X; - int StartY; - int WholePixelXMove; - int XDirection; - int ErrorTerm; - int ErrorTermAdjUp; - int ErrorTermAdjDown; - int Count; - }; +struct EdgeState { + struct EdgeState *NextEdge; + int X; + int StartY; + int WholePixelXMove; + int XDirection; + int ErrorTerm; + int ErrorTermAdjUp; + int ErrorTermAdjDown; + int Count; +}; - extern void DrawHorizontalLineSeg(int, int, int, int); - extern int FillConvexPolygon(struct PointListHeader *, int, int, int); - static void BuildGET(struct PointListHeader *, struct EdgeState *, int, int); - static void MoveXSortedToAET(int); - static void ScanOutAET(int, int); - static void AdvanceAET(void); - static void XSortAET(void); +extern void DrawHorizontalLineSeg(int, int, int, int); +extern int FillConvexPolygon(struct PointListHeader *, int, int, int); +static void BuildGET(struct PointListHeader *, struct EdgeState *, int, int); +static void MoveXSortedToAET(int); +static void ScanOutAET(int, int); +static void AdvanceAET(void); +static void XSortAET(void); - /* Pointers to global edge table (GET) and active edge table (AET) */ - static struct EdgeState *GETPtr, *AETPtr; +/* Pointers to global edge table (GET) and active edge table (AET) */ +static struct EdgeState *GETPtr, *AETPtr; - int FillPolygon(struct PointListHeader * VertexList, int Color, - int PolygonShape, int XOffset, int YOffset) - { - struct EdgeState *EdgeTableBuffer; - int CurrentY; +int FillPolygon(struct PointListHeader * VertexList, int Color, + int PolygonShape, int XOffset, int YOffset) +{ + struct EdgeState *EdgeTableBuffer; + int CurrentY; - #ifdef CONVEX_CODE_LINKED - /* Pass convex polygons through to fast convex polygon filler */ - if (PolygonShape == CONVEX) - return(FillConvexPolygon(VertexList, Color, XOffset, YOffset)); - #endif +#ifdef CONVEX_CODE_LINKED + /* Pass convex polygons through to fast convex polygon filler */ + if (PolygonShape == CONVEX) + return(FillConvexPolygon(VertexList, Color, XOffset, YOffset)); +#endif - /* It takes a minimum of 3 vertices to cause any pixels to be - drawn; reject polygons that are guaranteed to be invisible */ - if (VertexList->Length < 3) - return(1); - /* Get enough memory to store the entire edge table */ - if ((EdgeTableBuffer = - (struct EdgeState *) (malloc(sizeof(struct EdgeState) * - VertexList->Length))) == NULL) - 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, - so long as at least one edge remains in either the GET or AET */ - AETPtr = NULL; /* initialize the active edge table to empty */ - CurrentY = GETPtr->StartY; /* start at the top polygon vertex */ - while ((GETPtr != NULL) || (AETPtr != NULL)) { - MoveXSortedToAET(CurrentY); /* update AET for this scan line */ - ScanOutAET(CurrentY, Color); /* draw this scan line from AET */ - AdvanceAET(); /* advance AET edges 1 scan line */ - XSortAET(); /* resort on X */ - CurrentY++; /* advance to the next scan line */ - } - /* Release the memory we've allocated and we're done */ - free(EdgeTableBuffer); - return(1); + /* It takes a minimum of 3 vertices to cause any pixels to be + drawn; reject polygons that are guaranteed to be invisible */ + if (VertexList->Length < 3) + return(1); + /* Get enough memory to store the entire edge table */ + if ((EdgeTableBuffer = + (struct EdgeState *) (malloc(sizeof(struct EdgeState) * + VertexList->Length))) == NULL) + 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, + so long as at least one edge remains in either the GET or AET */ + AETPtr = NULL; /* initialize the active edge table to empty */ + CurrentY = GETPtr->StartY; /* start at the top polygon vertex */ + while ((GETPtr != NULL) || (AETPtr != NULL)) { + MoveXSortedToAET(CurrentY); /* update AET for this scan line */ + ScanOutAET(CurrentY, Color); /* draw this scan line from AET */ + AdvanceAET(); /* advance AET edges 1 scan line */ + XSortAET(); /* resort on X */ + CurrentY++; /* advance to the next scan line */ + } + /* Release the memory we've allocated and we're done */ + free(EdgeTableBuffer); + return(1); +} + +/* Creates a GET in the buffer pointed to by NextFreeEdgeStruc from + the vertex list. Edge endpoints are flipped, if necessary, to + guarantee all edges go top to bottom. The GET is sorted primarily + by ascending Y start coordinate, and secondarily by ascending X + start coordinate within edges with common Y coordinates. */ +static void BuildGET(struct PointListHeader * VertexList, + struct EdgeState * NextFreeEdgeStruc, int XOffset, int YOffset) +{ + int i, StartX, StartY, EndX, EndY, DeltaY, DeltaX, Width, temp; + struct EdgeState *NewEdgePtr; + struct EdgeState *FollowingEdge, **FollowingEdgeLink; + struct Point *VertexPtr; + + /* Scan through the vertex list and put all non-0-height edges into + the GET, sorted by increasing Y start coordinate */ + VertexPtr = VertexList->PointPtr; /* point to the vertex list */ + GETPtr = NULL; /* initialize the global edge table to empty */ + for (i = 0; i < VertexList->Length; i++) { + /* Calculate the edge height and width */ + StartX = VertexPtr[i].X + XOffset; + StartY = VertexPtr[i].Y + YOffset; + /* The edge runs from the current point to the previous one */ + if (i == 0) { + /* Wrap back around to the end of the list */ + EndX = VertexPtr[VertexList->Length-1].X + XOffset; + EndY = VertexPtr[VertexList->Length-1].Y + YOffset; + } else { + EndX = VertexPtr[i-1].X + XOffset; + EndY = VertexPtr[i-1].Y + YOffset; } - - /* Creates a GET in the buffer pointed to by NextFreeEdgeStruc from - the vertex list. Edge endpoints are flipped, if necessary, to - guarantee all edges go top to bottom. The GET is sorted primarily - by ascending Y start coordinate, and secondarily by ascending X - start coordinate within edges with common Y coordinates. */ - static void BuildGET(struct PointListHeader * VertexList, - struct EdgeState * NextFreeEdgeStruc, int XOffset, int YOffset) - { - int i, StartX, StartY, EndX, EndY, DeltaY, DeltaX, Width, temp; - struct EdgeState *NewEdgePtr; - struct EdgeState *FollowingEdge, **FollowingEdgeLink; - struct Point *VertexPtr; - - /* Scan through the vertex list and put all non-0-height edges into - the GET, sorted by increasing Y start coordinate */ - VertexPtr = VertexList->PointPtr; /* point to the vertex list */ - GETPtr = NULL; /* initialize the global edge table to empty */ - for (i = 0; i < VertexList->Length; i++) { - /* Calculate the edge height and width */ - StartX = VertexPtr[i].X + XOffset; - StartY = VertexPtr[i].Y + YOffset; - /* The edge runs from the current point to the previous one */ - if (i == 0) { - /* Wrap back around to the end of the list */ - EndX = VertexPtr[VertexList->Length-1].X + XOffset; - EndY = VertexPtr[VertexList->Length-1].Y + YOffset; - } else { - EndX = VertexPtr[i-1].X + XOffset; - EndY = VertexPtr[i-1].Y + YOffset; - } - /* Make sure the edge runs top to bottom */ - if (StartY > EndY) { - SWAP(StartX, EndX); - SWAP(StartY, EndY); - } - /* 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 - structure */ - NewEdgePtr = NextFreeEdgeStruc++; - NewEdgePtr->XDirection = /* direction in which X moves */ - ((DeltaX = EndX - StartX) > 0) ? 1 : -1; - Width = abs(DeltaX); - NewEdgePtr->X = StartX; - NewEdgePtr->StartY = StartY; - NewEdgePtr->Count = DeltaY; - NewEdgePtr->ErrorTermAdjDown = DeltaY; - if (DeltaX >= 0) /* initial error term going L->R */ - NewEdgePtr->ErrorTerm = 0; - else /* initial error term going R->L */ - NewEdgePtr->ErrorTerm = -DeltaY + 1; - if (DeltaY >= Width) { /* Y-major edge */ - NewEdgePtr->WholePixelXMove = 0; - NewEdgePtr->ErrorTermAdjUp = Width; - } else { /* X-major edge */ - NewEdgePtr->WholePixelXMove = - (Width / DeltaY) * NewEdgePtr->XDirection; - NewEdgePtr->ErrorTermAdjUp = Width % DeltaY; - } - /* Link the new edge into the GET so that the edge list is - still sorted by Y coordinate, and by X coordinate for all - edges with the same Y coordinate */ - FollowingEdgeLink = &GETPtr; - for (;;) { - FollowingEdge = *FollowingEdgeLink; - if ((FollowingEdge == NULL) || - (FollowingEdge->StartY > StartY) || - ((FollowingEdge->StartY == StartY) && - (FollowingEdge->X >= StartX))) { - NewEdgePtr->NextEdge = FollowingEdge; - *FollowingEdgeLink = NewEdgePtr; - break; - } - FollowingEdgeLink = &FollowingEdge->NextEdge; - } + /* Make sure the edge runs top to bottom */ + if (StartY > EndY) { + SWAP(StartX, EndX); + SWAP(StartY, EndY); + } + /* 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 + structure */ + NewEdgePtr = NextFreeEdgeStruc++; + NewEdgePtr->XDirection = /* direction in which X moves */ + ((DeltaX = EndX - StartX) > 0) ? 1 : -1; + Width = abs(DeltaX); + NewEdgePtr->X = StartX; + NewEdgePtr->StartY = StartY; + NewEdgePtr->Count = DeltaY; + NewEdgePtr->ErrorTermAdjDown = DeltaY; + if (DeltaX >= 0) /* initial error term going L->R */ + NewEdgePtr->ErrorTerm = 0; + else /* initial error term going R->L */ + NewEdgePtr->ErrorTerm = -DeltaY + 1; + if (DeltaY >= Width) { /* Y-major edge */ + NewEdgePtr->WholePixelXMove = 0; + NewEdgePtr->ErrorTermAdjUp = Width; + } else { /* X-major edge */ + NewEdgePtr->WholePixelXMove = + (Width / DeltaY) * NewEdgePtr->XDirection; + NewEdgePtr->ErrorTermAdjUp = Width % DeltaY; + } + /* Link the new edge into the GET so that the edge list is + still sorted by Y coordinate, and by X coordinate for all + edges with the same Y coordinate */ + FollowingEdgeLink = &GETPtr; + for (;;) { + FollowingEdge = *FollowingEdgeLink; + if ((FollowingEdge == NULL) || + (FollowingEdge->StartY > StartY) || + ((FollowingEdge->StartY == StartY) && + (FollowingEdge->X >= StartX))) { + NewEdgePtr->NextEdge = FollowingEdge; + *FollowingEdgeLink = NewEdgePtr; + break; } + FollowingEdgeLink = &FollowingEdge->NextEdge; } } + } +} - /* Sorts all edges currently in the active edge table into ascending - order of current X coordinates */ - static void XSortAET() { - struct EdgeState *CurrentEdge, **CurrentEdgePtr, *TempEdge; - int SwapOccurred; +/* Sorts all edges currently in the active edge table into ascending + order of current X coordinates */ +static void XSortAET() { + struct EdgeState *CurrentEdge, **CurrentEdgePtr, *TempEdge; + int SwapOccurred; - /* Scan through the AET and swap any adjacent edges for which the - second edge is at a lower current X coord than the first edge. - Repeat until no further swapping is needed */ - if (AETPtr != NULL) { - do { - SwapOccurred = 0; - CurrentEdgePtr = &AETPtr; - while ((CurrentEdge = *CurrentEdgePtr)->NextEdge != NULL) { - if (CurrentEdge->X > CurrentEdge->NextEdge->X) { - /* The second edge has a lower X than the first; - swap them in the AET */ - TempEdge = CurrentEdge->NextEdge->NextEdge; - *CurrentEdgePtr = CurrentEdge->NextEdge; - CurrentEdge->NextEdge->NextEdge = CurrentEdge; - CurrentEdge->NextEdge = TempEdge; - SwapOccurred = 1; - } - CurrentEdgePtr = &(*CurrentEdgePtr)->NextEdge; - } - } while (SwapOccurred != 0); - } - } - - /* Advances each edge in the AET by one scan line. - Removes edges that have been fully scanned. */ - static void AdvanceAET() { - struct EdgeState *CurrentEdge, **CurrentEdgePtr; - - /* Count down and remove or advance each edge in the AET */ + /* Scan through the AET and swap any adjacent edges for which the + second edge is at a lower current X coord than the first edge. + Repeat until no further swapping is needed */ + if (AETPtr != NULL) { + do { + SwapOccurred = 0; CurrentEdgePtr = &AETPtr; - while ((CurrentEdge = *CurrentEdgePtr) != NULL) { - /* Count off one scan line for this edge */ - if ((--(CurrentEdge->Count)) == 0) { - /* This edge is finished, so remove it from the AET */ + while ((CurrentEdge = *CurrentEdgePtr)->NextEdge != NULL) { + if (CurrentEdge->X > CurrentEdge->NextEdge->X) { + /* The second edge has a lower X than the first; + swap them in the AET */ + TempEdge = CurrentEdge->NextEdge->NextEdge; *CurrentEdgePtr = CurrentEdge->NextEdge; - } else { - /* Advance the edge's X coordinate by minimum move */ - CurrentEdge->X += CurrentEdge->WholePixelXMove; - /* Determine whether it's time for X to advance one extra */ - if ((CurrentEdge->ErrorTerm += - CurrentEdge->ErrorTermAdjUp) > 0) { - CurrentEdge->X += CurrentEdge->XDirection; - CurrentEdge->ErrorTerm -= CurrentEdge->ErrorTermAdjDown; - } - CurrentEdgePtr = &CurrentEdge->NextEdge; + CurrentEdge->NextEdge->NextEdge = CurrentEdge; + CurrentEdge->NextEdge = TempEdge; + SwapOccurred = 1; } + CurrentEdgePtr = &(*CurrentEdgePtr)->NextEdge; + } + } while (SwapOccurred != 0); + } +} + +/* Advances each edge in the AET by one scan line. + Removes edges that have been fully scanned. */ +static void AdvanceAET() { + struct EdgeState *CurrentEdge, **CurrentEdgePtr; + + /* Count down and remove or advance each edge in the AET */ + CurrentEdgePtr = &AETPtr; + while ((CurrentEdge = *CurrentEdgePtr) != NULL) { + /* Count off one scan line for this edge */ + if ((--(CurrentEdge->Count)) == 0) { + /* This edge is finished, so remove it from the AET */ + *CurrentEdgePtr = CurrentEdge->NextEdge; + } else { + /* Advance the edge's X coordinate by minimum move */ + CurrentEdge->X += CurrentEdge->WholePixelXMove; + /* Determine whether it's time for X to advance one extra */ + if ((CurrentEdge->ErrorTerm += + CurrentEdge->ErrorTermAdjUp) > 0) { + CurrentEdge->X += CurrentEdge->XDirection; + CurrentEdge->ErrorTerm -= CurrentEdge->ErrorTermAdjDown; + } + CurrentEdgePtr = &CurrentEdge->NextEdge; + } + } +} + +/* Moves all edges that start at the specified Y coordinate from the + GET to the AET, maintaining the X sorting of the AET. */ +static void MoveXSortedToAET(int YToMove) { + struct EdgeState *AETEdge, **AETEdgePtr, *TempEdge; + 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 + 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 + guaranteed to belong later in the AET than the one just added. */ + AETEdgePtr = &AETPtr; + while ((GETPtr != NULL) && (GETPtr->StartY == YToMove)) { + CurrentX = GETPtr->X; + /* Link the new edge into the AET so that the AET is still + sorted by X coordinate */ + for (;;) { + AETEdge = *AETEdgePtr; + if ((AETEdge == NULL) || (AETEdge->X >= CurrentX)) { + TempEdge = GETPtr->NextEdge; + *AETEdgePtr = GETPtr; /* link the edge into the AET */ + GETPtr->NextEdge = AETEdge; + AETEdgePtr = &GETPtr->NextEdge; + GETPtr = TempEdge; /* unlink the edge from the GET */ + break; + } else { + AETEdgePtr = &AETEdge->NextEdge; } } + } +} - /* Moves all edges that start at the specified Y coordinate from the - GET to the AET, maintaining the X sorting of the AET. */ - static void MoveXSortedToAET(int YToMove) { - struct EdgeState *AETEdge, **AETEdgePtr, *TempEdge; - int CurrentX; +/* Fills the scan line described by the current AET at the specified Y + coordinate in the specified color, using the odd/even fill rule */ +static void ScanOutAET(int YToScan, int Color) { + int LeftX; + struct EdgeState *CurrentEdge; - /* 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 - 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 - guaranteed to belong later in the AET than the one just added. */ - AETEdgePtr = &AETPtr; - while ((GETPtr != NULL) && (GETPtr->StartY == YToMove)) { - CurrentX = GETPtr->X; - /* Link the new edge into the AET so that the AET is still - sorted by X coordinate */ - for (;;) { - AETEdge = *AETEdgePtr; - if ((AETEdge == NULL) || (AETEdge->X >= CurrentX)) { - TempEdge = GETPtr->NextEdge; - *AETEdgePtr = GETPtr; /* link the edge into the AET */ - GETPtr->NextEdge = AETEdge; - AETEdgePtr = &GETPtr->NextEdge; - GETPtr = TempEdge; /* unlink the edge from the GET */ - break; - } else { - AETEdgePtr = &AETEdge->NextEdge; - } - } - } - } - - /* Fills the scan line described by the current AET at the specified Y - coordinate in the specified color, using the odd/even fill rule */ - static void ScanOutAET(int YToScan, int Color) { - int LeftX; - struct EdgeState *CurrentEdge; - - /* Scan through the AET, drawing line segments as each pair of edge - crossings is encountered. The nearest pixel on or to the right - of left edges is drawn, and the nearest pixel to the left of but - not on right edges is drawn */ - CurrentEdge = AETPtr; - while (CurrentEdge != NULL) { - LeftX = CurrentEdge->X; - CurrentEdge = CurrentEdge->NextEdge; - DrawHorizontalLineSeg(YToScan, LeftX, CurrentEdge->X-1, Color); - CurrentEdge = CurrentEdge->NextEdge; - } - } + /* Scan through the AET, drawing line segments as each pair of edge + crossings is encountered. The nearest pixel on or to the right + of left edges is drawn, and the nearest pixel to the left of but + not on right edges is drawn */ + CurrentEdge = AETPtr; + while (CurrentEdge != NULL) { + LeftX = CurrentEdge->X; + CurrentEdge = CurrentEdge->NextEdge; + DrawHorizontalLineSeg(YToScan, LeftX, CurrentEdge->X-1, Color); + CurrentEdge = CurrentEdge->NextEdge; + } +} +``` diff --git a/40-03.md b/40-03.md index 522bb03..ae949b5 100644 --- a/40-03.md +++ b/40-03.md @@ -33,155 +33,161 @@ various sorts. **LISTING 40.2 L40-2.C** - /* 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 - drawing will take place if LeftX > RightX. */ +```c +/* 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 + drawing will take place if LeftX > RightX. */ - #include - #include "polygon.h" +#include +#include "polygon.h" - #define SCREEN_WIDTH 320 - #define SCREEN_SEGMENT 0xA000 +#define SCREEN_WIDTH 320 +#define SCREEN_SEGMENT 0xA000 - static void DrawPixel(int, int, int); +static void DrawPixel(int, int, int); - void DrawHorizontalLineSeg(Y, LeftX, RightX, Color) { - int X; +void DrawHorizontalLineSeg(Y, LeftX, RightX, Color) { + int X; - /* Draw each pixel in the horizontal line segment, starting with - the leftmost one */ - for (X = LeftX; X <= RightX; X++) - DrawPixel(X, Y, Color); - } + /* Draw each pixel in the horizontal line segment, starting with + the leftmost one */ + for (X = LeftX; X <= RightX; X++) + DrawPixel(X, Y, Color); +} - /* Draws the pixel at (X, Y) in color Color in VGA mode 13h */ - static void DrawPixel(int X, int Y, int Color) { - unsigned char far *ScreenPtr; +/* Draws the pixel at (X, Y) in color Color in VGA mode 13h */ +static void DrawPixel(int X, int Y, int Color) { + unsigned char far *ScreenPtr; - #ifdef __TURBOC__ - ScreenPtr = MK_FP(SCREEN_SEGMENT, Y * SCREEN_WIDTH + X); - #else /* MSC 5.0 */ - FP_SEG(ScreenPtr) = SCREEN_SEGMENT; - FP_OFF(ScreenPtr) = Y * SCREEN_WIDTH + X; - #endif - *ScreenPtr = (unsigned char) Color; - } +#ifdef __TURBOC__ + ScreenPtr = MK_FP(SCREEN_SEGMENT, Y * SCREEN_WIDTH + X); +#else /* MSC 5.0 */ + FP_SEG(ScreenPtr) = SCREEN_SEGMENT; + FP_OFF(ScreenPtr) = Y * SCREEN_WIDTH + X; +#endif + *ScreenPtr = (unsigned char) Color; +} +``` **LISTING 40.3 POLYGON.H** - /* POLYGON.H: Header file for polygon-filling code */ +```c +/* POLYGON.H: Header file for polygon-filling code */ - #define CONVEX 0 - #define NONCONVEX 1 - #define COMPLEX 2 +#define CONVEX 0 +#define NONCONVEX 1 +#define COMPLEX 2 - /* Describes a single point (used for a single vertex) */ - struct Point { - int X; /* X coordinate */ - int Y; /* Y coordinate */ - }; - /* Describes a series of points (used to store a list of vertices that - describe a polygon; each vertex connects to the two adjacent - vertices; the last vertex is assumed to connect to the first) */ - struct PointListHeader { - int Length; /* # of points */ - struct Point * PointPtr; /* pointer to list of points */ - }; - /* Describes the beginning and ending X coordinates of a single - horizontal line (used only by fast polygon fill code) */ - struct HLine { - int XStart; /* X coordinate of leftmost pixel in line */ - int XEnd; /* X coordinate of rightmost pixel in line */ - }; - /* Describes a length-long series of horizontal lines, all assumed to - be on contiguous scan lines starting at YStart and proceeding - downward (used to describe a scan-converted polygon to the - low-level hardware-dependent drawing code) (used only by fast - polygon fill code). */ - struct HLineList { - int Length; /* # of horizontal lines */ - int YStart; /* Y coordinate of topmost line */ - struct HLine * HLinePtr; /* pointer to list of horz lines */ - }; +/* Describes a single point (used for a single vertex) */ +struct Point { + int X; /* X coordinate */ + int Y; /* Y coordinate */ +}; +/* Describes a series of points (used to store a list of vertices that + describe a polygon; each vertex connects to the two adjacent + vertices; the last vertex is assumed to connect to the first) */ +struct PointListHeader { + int Length; /* # of points */ + struct Point * PointPtr; /* pointer to list of points */ +}; +/* Describes the beginning and ending X coordinates of a single + horizontal line (used only by fast polygon fill code) */ +struct HLine { + int XStart; /* X coordinate of leftmost pixel in line */ + int XEnd; /* X coordinate of rightmost pixel in line */ +}; +/* Describes a length-long series of horizontal lines, all assumed to + be on contiguous scan lines starting at YStart and proceeding + downward (used to describe a scan-converted polygon to the + low-level hardware-dependent drawing code) (used only by fast + polygon fill code). */ +struct HLineList { + int Length; /* # of horizontal lines */ + int YStart; /* Y coordinate of topmost line */ + struct HLine * HLinePtr; /* pointer to list of horz lines */ +}; +``` **LISTING 40.4 L40-4.C** - /* Sample program to exercise the polygon-filling routines */ +```c +/* Sample program to exercise the polygon-filling routines */ - #include - #include - #include "polygon.h" +#include +#include +#include "polygon.h" - #define DRAW_POLYGON(PointList,Color,Shape,X,Y) \ - Polygon.Length = sizeof(PointList)/sizeof(struct Point); \ - Polygon.PointPtr = PointList; \ - FillPolygon(&Polygon, Color, Shape, X, Y); +#define DRAW_POLYGON(PointList,Color,Shape,X,Y) \ + Polygon.Length = sizeof(PointList)/sizeof(struct Point); \ + Polygon.PointPtr = PointList; \ + FillPolygon(&Polygon, Color, Shape, X, Y); - void main(void); - extern int FillPolygon(struct PointListHeader *, int, int, int, int); +void main(void); +extern int FillPolygon(struct PointListHeader *, int, int, int, int); - void main() { - int i, j; - struct PointListHeader Polygon; - static struct Point Polygon1[] = - {{0,0},{100,150},{320,0},{0,200},{220,50},{320,200}}; - static struct Point Polygon2[] = - {{0,0},{320,0},{320,200},{0,200},{0,0},{50,50}, - {270,50},{270,150},{50,150},{50,50}}; - static struct Point Polygon3[] = - {{0,0},{10,0},{105,185},{260,30},{15,150},{5,150},{5,140}, - {260,5},{300,5},{300,15},{110,200},{100,200},{0,10}}; - static struct Point Polygon4[] = - {{0,0},{30,-20},{30,0},{0,20},{-30,0},{-30,-20}}; - static struct Point Triangle1[] = {{30,0},{15,20},{0,0}}; - static struct Point Triangle2[] = {{30,20},{15,0},{0,20}}; - static struct Point Triangle3[] = {{0,20},{20,10},{0,0}}; - static struct Point Triangle4[] = {{20,20},{20,0},{0,10}}; - union REGS regset; +void main() { + int i, j; + struct PointListHeader Polygon; + static struct Point Polygon1[] = + {{0,0},{100,150},{320,0},{0,200},{220,50},{320,200}}; + static struct Point Polygon2[] = + {{0,0},{320,0},{320,200},{0,200},{0,0},{50,50}, + {270,50},{270,150},{50,150},{50,50}}; + static struct Point Polygon3[] = + {{0,0},{10,0},{105,185},{260,30},{15,150},{5,150},{5,140}, + {260,5},{300,5},{300,15},{110,200},{100,200},{0,10}}; + static struct Point Polygon4[] = + {{0,0},{30,-20},{30,0},{0,20},{-30,0},{-30,-20}}; + static struct Point Triangle1[] = {{30,0},{15,20},{0,0}}; + static struct Point Triangle2[] = {{30,20},{15,0},{0,20}}; + static struct Point Triangle3[] = {{0,20},{20,10},{0,0}}; + static struct Point Triangle4[] = {{20,20},{20,0},{0,10}}; + union REGS regset; - /* Set the display to VGA mode 13h, 320x200 256-color mode */ - regset.x.ax = 0x0013; - int86(0x10, ®set, ®set); + /* Set the display to VGA mode 13h, 320x200 256-color mode */ + regset.x.ax = 0x0013; + int86(0x10, ®set, ®set); - /* Draw three complex polygons */ - DRAW_POLYGON(Polygon1, 15, COMPLEX, 0, 0); - getch(); /* wait for a keypress */ - DRAW_POLYGON(Polygon2, 5, COMPLEX, 0, 0); - getch(); /* wait for a keypress */ - DRAW_POLYGON(Polygon3, 3, COMPLEX, 0, 0); - getch(); /* wait for a keypress */ + /* Draw three complex polygons */ + DRAW_POLYGON(Polygon1, 15, COMPLEX, 0, 0); + getch(); /* wait for a keypress */ + DRAW_POLYGON(Polygon2, 5, COMPLEX, 0, 0); + getch(); /* wait for a keypress */ + DRAW_POLYGON(Polygon3, 3, COMPLEX, 0, 0); + getch(); /* wait for a keypress */ - /* Draw some adjacent nonconvex polygons */ - for (i=0; i<5; i++) { - for (j=0; j<8; j++) { - DRAW_POLYGON(Polygon4, 16+i*8+j, NONCONVEX, 40+(i*60), - 30+(j*20)); - } - } - getch(); /* wait for a keypress */ - - /* Draw adjacent triangles across the screen */ - for (j=0; j<=80; j+=20) { - for (i=0; i<290; i += 30) { - DRAW_POLYGON(Triangle1, 2, CONVEX, i, j); - DRAW_POLYGON(Triangle2, 4, CONVEX, i+15, j); - } - } - for (j=100; j<=170; j+=20) { - /* Do a row of pointing-right triangles */ - for (i=0; i<290; i += 20) { - DRAW_POLYGON(Triangle3, 40, CONVEX, i, j); - } - /* Do a row of pointing-left triangles halfway between one row - of pointing-right triangles and the next, to fit between */ - for (i=0; i<290; i += 20) { - DRAW_POLYGON(Triangle4, 1, CONVEX, i, j+10); - } - } - getch(); /* wait for a keypress */ - - /* Return to text mode and exit */ - regset.x.ax = 0x0003; - int86(0x10, ®set, ®set); + /* Draw some adjacent nonconvex polygons */ + for (i=0; i<5; i++) { + for (j=0; j<8; j++) { + DRAW_POLYGON(Polygon4, 16+i*8+j, NONCONVEX, 40+(i*60), + 30+(j*20)); } + } + getch(); /* wait for a keypress */ + + /* Draw adjacent triangles across the screen */ + for (j=0; j<=80; j+=20) { + for (i=0; i<290; i += 30) { + DRAW_POLYGON(Triangle1, 2, CONVEX, i, j); + DRAW_POLYGON(Triangle2, 4, CONVEX, i+15, j); + } + } + for (j=100; j<=170; j+=20) { + /* Do a row of pointing-right triangles */ + for (i=0; i<290; i += 20) { + DRAW_POLYGON(Triangle3, 40, CONVEX, i, j); + } + /* Do a row of pointing-left triangles halfway between one row + of pointing-right triangles and the next, to fit between */ + for (i=0; i<290; i += 20) { + DRAW_POLYGON(Triangle4, 1, CONVEX, i, j+10); + } + } + getch(); /* wait for a keypress */ + + /* Return to text mode and exit */ + regset.x.ax = 0x0003; + int86(0x10, ®set, ®set); +} +``` diff --git a/40-04.md b/40-04.md index bc797bf..d0354b0 100644 --- a/40-04.md +++ b/40-04.md @@ -66,55 +66,57 @@ list processing) could pay off reasonably well. **LISTING 40.5 L40-5.ASM** - ; 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 - ; LeftX > RightX. Tested with TASM - ; C near-callable as: - ; void DrawHorizontalLineSeg(Y, LeftX, RightX, Color); +```nasm +; 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 +; LeftX > RightX. Tested with TASM +; C near-callable as: +; void DrawHorizontalLineSeg(Y, LeftX, RightX, Color); - SCREEN_WIDTH equ 320 - SCREEN_SEGMENT equ 0a000h +SCREEN_WIDTH equ 320 +SCREEN_SEGMENT equ 0a000h - Parms struc - dw 2 dup(?);return address & pushed BP - Y dw ? ;Y coordinate of line segment to draw - LeftX dw ? ;left endpoint of the line segment - RightX dw ? ;right endpoint of the line segment - Color dw ? ;color in which to draw the line segment - Parms ends +Parms struc + dw 2 dup(?);return address & pushed BP +Y dw ? ;Y coordinate of line segment to draw +LeftX dw ? ;left endpoint of the line segment +RightX dw ? ;right endpoint of the line segment +Color dw ? ;color in which to draw the line segment +Parms ends - .model small - .code - public _DrawHorizontalLineSeg - align 2 - _DrawHorizontalLineSeg proc - push bp ;preserve caller's stack frame - mov bp,sp ;point to our stack frame - 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 - mov di,[bp+LeftX] - mov cx,[bp+RightX] - sub cx,di ;width of line - jl DrawDone ;RightX < LeftX; no drawing to do - inc cx ;include both endpoints - mov ax,SCREEN_WIDTH - mul [bp+Y] ;offset of scan line on which to draw - add di,ax ;ES:DI points to start of line seg - mov al,byte ptr [bp+Color] ;color in which to draw - mov ah,al ;put color in AH for STOSW - shr cx,1 ;# of words to fill - rep stosw ;fill a word at a time - 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 - ret - _DrawHorizontalLineSeg endp - end + .model small + .code + public _DrawHorizontalLineSeg + align 2 +_DrawHorizontalLineSeg proc + push bp ;preserve caller's stack frame + mov bp,sp ;point to our stack frame + 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 + mov di,[bp+LeftX] + mov cx,[bp+RightX] + sub cx,di ;width of line + jl DrawDone ;RightX < LeftX; no drawing to do + inc cx ;include both endpoints + mov ax,SCREEN_WIDTH + mul [bp+Y] ;offset of scan line on which to draw + add di,ax ;ES:DI points to start of line seg + mov al,byte ptr [bp+Color] ;color in which to draw + mov ah,al ;put color in AH for STOSW + shr cx,1 ;# of words to fill + rep stosw ;fill a word at a time + 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 + ret +_DrawHorizontalLineSeg endp + end +``` The algorithm used to X-sort the AET is an interesting performance consideration. Listing 40.1 uses a bubble sort, usually a poor choice diff --git a/41-01.md b/41-01.md index a1b13a9..6704dc6 100644 --- a/41-01.md +++ b/41-01.md @@ -102,45 +102,47 @@ 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 - (non-self-intersecting) or not. Tested with Borland C++ in small model. */ +```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 +(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)) +#define SIGNUM(a) ((a>0)?1:((a<0)?-1:0)) - int PolygonIsMonotoneVertical(struct PointListHeader * VertexList) - { - int i, Length, DeltaYSign, PreviousDeltaYSign; - int NumYReversals = 0; - struct Point *VertexPtr = VertexList->PointPtr; +int PolygonIsMonotoneVertical(struct PointListHeader * VertexList) +{ + int i, Length, DeltaYSign, PreviousDeltaYSign; + int NumYReversals = 0; + struct Point *VertexPtr = VertexList->PointPtr; - /* Three or fewer points can't make a non-vertical-monotone polygon */ - if ((Length=VertexList->Length) < 4) return(1); + /* 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 */ - PreviousDeltaYSign = SIGNUM(VertexPtr[Length-1].Y - VertexPtr[0].Y); - i = 0; - while ((PreviousDeltaYSign == 0) && (i < (Length-1))) { - PreviousDeltaYSign = SIGNUM(VertexPtr[i].Y - VertexPtr[i+1].Y); - i++; - } + /* Scan to the first non-horizontal edge */ + PreviousDeltaYSign = SIGNUM(VertexPtr[Length-1].Y - VertexPtr[0].Y); + i = 0; + while ((PreviousDeltaYSign == 0) && (i < (Length-1))) { + PreviousDeltaYSign = SIGNUM(VertexPtr[i].Y - VertexPtr[i+1].Y); + i++; + } - if (i == (Length-1)) return(1); /* polygon is a flat line */ + 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 */ - do { - if ((DeltaYSign = SIGNUM(VertexPtr[i].Y - VertexPtr[i+1].Y)) - != 0) { - if (DeltaYSign != PreviousDeltaYSign) { - /* Switched Y direction; not vertical-monotone if - reversed Y direction as many as three times */ - if (++NumYReversals > 2) return(0); - PreviousDeltaYSign = DeltaYSign; - } - } - } while (i++ < (Length-1)); - return(1); /* it's a vertical-monotone polygon */ - } + /* 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 */ + do { + if ((DeltaYSign = SIGNUM(VertexPtr[i].Y - VertexPtr[i+1].Y)) + != 0) { + if (DeltaYSign != PreviousDeltaYSign) { + /* Switched Y direction; not vertical-monotone if + reversed Y direction as many as three times */ + if (++NumYReversals > 2) return(0); + PreviousDeltaYSign = DeltaYSign; + } + } + } while (i++ < (Length-1)); + return(1); /* it's a vertical-monotone polygon */ +} +``` diff --git a/41-02.md b/41-02.md index ab34d27..9f24166 100644 --- a/41-02.md +++ b/41-02.md @@ -21,118 +21,120 @@ 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 - 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 - you happier!) - ******************************************************************* - NOTE: the low-level drawing routine, DrawHorizontalLineList, must be able to - reverse the edges, if necessary to make the correct edge left edge. It must - also expect right edge to be specified in +1 format (the X coordinate is 1 past - 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. - ******************************************************************* - Returns 1 for success, 0 if memory allocation failed */ +```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 +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 +you happier!) +******************************************************************* +NOTE: the low-level drawing routine, DrawHorizontalLineList, must be able to +reverse the edges, if necessary to make the correct edge left edge. It must +also expect right edge to be specified in +1 format (the X coordinate is 1 past +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. +******************************************************************* +Returns 1 for success, 0 if memory allocation failed */ - #include - #include - #include - #include "polygon.h" +#include +#include +#include +#include "polygon.h" - /* Advances the index by one vertex forward through the vertex list, - wrapping at the end of the list */ - #define INDEX_FORWARD(Index) \ - Index = (Index + 1) % VertexList->Length; +/* Advances the index by one vertex forward through the vertex list, +wrapping at the end of the list */ +#define INDEX_FORWARD(Index) \ + Index = (Index + 1) % VertexList->Length; - /* Advances the index by one vertex backward through the vertex list, - wrapping at the start of the list */ - #define INDEX_BACKWARD(Index) \ - Index = (Index - 1 + VertexList->Length) % VertexList->Length; +/* Advances the index by one vertex backward through the vertex list, +wrapping at the start of the list */ +#define INDEX_BACKWARD(Index) \ + Index = (Index - 1 + VertexList->Length) % VertexList->Length; - /* Advances the index by one vertex either forward or backward through - the vertex list, wrapping at either end of the list */ - #define INDEX_MOVE(Index,Direction) \ - if (Direction > 0) \ - Index = (Index + 1) % VertexList->Length; \ - else \ - Index = (Index - 1 + VertexList->Length) % VertexList->Length; +/* Advances the index by one vertex either forward or backward through +the vertex list, wrapping at either end of the list */ +#define INDEX_MOVE(Index,Direction) \ + if (Direction > 0) \ + Index = (Index + 1) % VertexList->Length; \ + else \ + Index = (Index - 1 + VertexList->Length) % VertexList->Length; - extern void ScanEdge(int, int, int, int, int, int, struct HLine **); - extern void DrawHorizontalLineList(struct HLineList *, int); +extern void ScanEdge(int, int, int, int, int, int, struct HLine **); +extern void DrawHorizontalLineList(struct HLineList *, int); - int FillMonotoneVerticalPolygon(struct PointListHeader * VertexList, - int Color, int XOffset, int YOffset) - { - int i, MinIndex, MaxIndex, MinPoint_Y, MaxPoint_Y; - int NextIndex, CurrentIndex, PreviousIndex; - struct HLineList WorkingHLineList; - struct HLine *EdgePointPtr; - struct Point *VertexPtr; +int FillMonotoneVerticalPolygon(struct PointListHeader * VertexList, + int Color, int XOffset, int YOffset) +{ + int i, MinIndex, MaxIndex, MinPoint_Y, MaxPoint_Y; + int NextIndex, CurrentIndex, PreviousIndex; + struct HLineList WorkingHLineList; + struct HLine *EdgePointPtr; + struct Point *VertexPtr; - /* Point to the vertex list */ - VertexPtr = VertexList->PointPtr; + /* Point to the vertex list */ + VertexPtr = VertexList->PointPtr; - /* Scan the list to find the top and bottom of the polygon */ - if (VertexList->Length == 0) - return(1); /* reject null polygons */ - MaxPoint_Y = MinPoint_Y = VertexPtr[MinIndex = MaxIndex = 0].Y; - for (i = 1; i < VertexList->Length; i++) { - if (VertexPtr[i].Y < MinPoint_Y) - MinPoint_Y = VertexPtr[MinIndex = i].Y; /* new top */ - else if (VertexPtr[i].Y > MaxPoint_Y) - MaxPoint_Y = VertexPtr[MaxIndex = i].Y; /* new bottom */ - } + /* Scan the list to find the top and bottom of the polygon */ + if (VertexList->Length == 0) + return(1); /* reject null polygons */ + MaxPoint_Y = MinPoint_Y = VertexPtr[MinIndex = MaxIndex = 0].Y; + for (i = 1; i < VertexList->Length; i++) { + if (VertexPtr[i].Y < MinPoint_Y) + MinPoint_Y = VertexPtr[MinIndex = i].Y; /* new top */ + else if (VertexPtr[i].Y > MaxPoint_Y) + MaxPoint_Y = VertexPtr[MaxIndex = i].Y; /* new bottom */ + } - /* 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 */ - WorkingHLineList.YStart = YOffset + MinPoint_Y; + /* 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 */ + 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 */ + /* 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 */ - /* Scan the first edge and store the boundary points in the list */ - /* Initial pointer for storing scan converted first-edge coords */ - EdgePointPtr = WorkingHLineList.HLinePtr; - /* Start from the top of the first edge */ - PreviousIndex = CurrentIndex = MinIndex; - /* Scan convert each line in the first edge from top to bottom */ - do { - INDEX_BACKWARD(CurrentIndex); - ScanEdge(VertexPtr[PreviousIndex].X + XOffset, - VertexPtr[PreviousIndex].Y, - VertexPtr[CurrentIndex].X + XOffset, - VertexPtr[CurrentIndex].Y, 1, 0, &EdgePointPtr); - PreviousIndex = CurrentIndex; - } while (CurrentIndex != MaxIndex); + /* Scan the first edge and store the boundary points in the list */ + /* Initial pointer for storing scan converted first-edge coords */ + EdgePointPtr = WorkingHLineList.HLinePtr; + /* Start from the top of the first edge */ + PreviousIndex = CurrentIndex = MinIndex; + /* Scan convert each line in the first edge from top to bottom */ + do { + INDEX_BACKWARD(CurrentIndex); + ScanEdge(VertexPtr[PreviousIndex].X + XOffset, + VertexPtr[PreviousIndex].Y, + VertexPtr[CurrentIndex].X + XOffset, + VertexPtr[CurrentIndex].Y, 1, 0, &EdgePointPtr); + PreviousIndex = CurrentIndex; + } while (CurrentIndex != MaxIndex); - /* Scan the second edge and store the boundary points in the list */ - EdgePointPtr = WorkingHLineList.HLinePtr; - PreviousIndex = CurrentIndex = MinIndex; - /* Scan convert the second edge, top to bottom */ - do { - INDEX_FORWARD(CurrentIndex); - ScanEdge(VertexPtr[PreviousIndex].X + XOffset, - VertexPtr[PreviousIndex].Y, - VertexPtr[CurrentIndex].X + XOffset, - VertexPtr[CurrentIndex].Y, 0, 0, &EdgePointPtr); - PreviousIndex = CurrentIndex; - } while (CurrentIndex != MaxIndex); + /* Scan the second edge and store the boundary points in the list */ + EdgePointPtr = WorkingHLineList.HLinePtr; + PreviousIndex = CurrentIndex = MinIndex; + /* Scan convert the second edge, top to bottom */ + do { + INDEX_FORWARD(CurrentIndex); + ScanEdge(VertexPtr[PreviousIndex].X + XOffset, + VertexPtr[PreviousIndex].Y, + VertexPtr[CurrentIndex].X + XOffset, + VertexPtr[CurrentIndex].Y, 0, 0, &EdgePointPtr); + PreviousIndex = CurrentIndex; + } while (CurrentIndex != MaxIndex); - /* Draw the line list representing the scan converted polygon */ - DrawHorizontalLineList(&WorkingHLineList, Color); + /* Draw the line list representing the scan converted polygon */ + DrawHorizontalLineList(&WorkingHLineList, Color); - /* Release the line list's memory and we're successfully done */ - free(WorkingHLineList.HLinePtr); - return(1); - } + /* 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 b7a31ec..8ac171f 100644 --- a/41-03.md +++ b/41-03.md @@ -12,96 +12,98 @@ pages: 764-766 **LISTING 41.3 L41-3.ASM** - ; 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 - ; XStart < XEnd. Expects whichever edge is rightmost on any scan line to be in - ; +1 format; that is, XEnd is 1 greater than rightmost pixel to draw. If - ; XStart == XEnd, nothing is drawn on that scan line. - ; ****************************************************************** - ; C near-callable as: - ; void DrawHorizontalLineList(struct HLineList * HLineListPtr, int Color); - ; All assembly code tested with TASM and MASM +```nasm +; 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 +; XStart < XEnd. Expects whichever edge is rightmost on any scan line to be in +; +1 format; that is, XEnd is 1 greater than rightmost pixel to draw. If +; XStart == XEnd, nothing is drawn on that scan line. +; ****************************************************************** +; C near-callable as: +; void DrawHorizontalLineList(struct HLineList * HLineListPtr, int Color); +; All assembly code tested with TASM and MASM - SCREEN_WIDTH equ 320 - SCREEN_SEGMENT equ 0a000h +SCREEN_WIDTH equ 320 +SCREEN_SEGMENT equ 0a000h - HLine struc - XStart dw ? ;X coordinate of leftmost pixel in line - XEnd dw ? ;X coordinate of rightmost pixel in line - HLine ends +HLine struc +XStart dw ? ;X coordinate of leftmost pixel in line +XEnd dw ? ;X coordinate of rightmost pixel in line +HLine ends - HLineList struc - Lngth dw ? ;# of horizontal lines - YStart dw ? ;Y coordinate of topmost line - HLinePtr dw ? ;pointer to list of horz lines - HLineList ends +HLineList struc +Lngth dw ? ;# of horizontal lines +YStart dw ? ;Y coordinate of topmost line +HLinePtr dw ? ;pointer to list of horz lines +HLineList ends - Parms struc - dw 2 dup(?) ;return address & pushed BP - HLineListPtr dw ? ;pointer to HLineList structure - Color dw ? ;color with which to fill - Parms ends - .model small - .code - public _DrawHorizontalLineList - align 2 - _DrawHorizontalLineList proc - push bp ;preserve caller's stack frame - mov bp,sp ;point to our stack frame - push si ;preserve caller's register variables - push di - cld ;make string instructions inc pointers +Parms struc + dw 2 dup(?) ;return address & pushed BP +HLineListPtr dw ? ;pointer to HLineList structure +Color dw ? ;color with which to fill +Parms ends + .model small + .code + public _DrawHorizontalLineList + align 2 +_DrawHorizontalLineList proc + push bp ;preserve caller's stack frame + mov bp,sp ;point to our stack frame + push si ;preserve caller's register variables + push di + cld ;make string instructions inc pointers - mov ax,SCREEN_SEGMENT - mov es,ax ;point ES to display memory for REP STOS + mov ax,SCREEN_SEGMENT + mov es,ax ;point ES to display memory for REP STOS - mov si,[bp+HLineListPtr] ;point to the line list - mov ax,SCREEN_WIDTH ;point to the start of the first scan - mul [si+YStart] ; line in which to draw - mov dx,ax ;ES:DX points to first scan line to draw - mov bx,[si+HLinePtr] ;point to the XStart/XEnd descriptor - ; 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 - 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 - xchg di,cx ;yes, so swap edges - NoSwap: - sub cx,di ;width of fill on this line - jz LineFillDone ;skip if zero width - add di,dx ;offset of left edge of fill - test di,1 ;does fill start at an odd address? - jz MainFill ;no - stosb ;yes, draw the odd leading byte to - ; word-align the rest of the fill - dec cx ;count off the odd leading byte - jz LineFillDone ;done if that was the only byte - 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 - ; do, 0 otherwise - rep stosb ;fill any odd trailing byte - LineFillDone: - add bx,size HLine ;point to the next line descriptor - add dx,SCREEN_WIDTH ;point to the next scan line - dec si ;count off lines to fill - jnz FillLoop - FillDone: - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - _DrawHorizontalLineList endp - end + mov si,[bp+HLineListPtr] ;point to the line list + mov ax,SCREEN_WIDTH ;point to the start of the first scan + mul [si+YStart] ; line in which to draw + mov dx,ax ;ES:DX points to first scan line to draw + mov bx,[si+HLinePtr] ;point to the XStart/XEnd descriptor + ; 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 + 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 + xchg di,cx ;yes, so swap edges +NoSwap: + sub cx,di ;width of fill on this line + jz LineFillDone ;skip if zero width + add di,dx ;offset of left edge of fill + test di,1 ;does fill start at an odd address? + jz MainFill ;no + stosb ;yes, draw the odd leading byte to + ; word-align the rest of the fill + dec cx ;count off the odd leading byte + jz LineFillDone ;done if that was the only byte +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 + ; do, 0 otherwise + rep stosb ;fill any odd trailing byte +LineFillDone: + add bx,size HLine ;point to the next line descriptor + add dx,SCREEN_WIDTH ;point to the next scan line + dec si ;count off lines to fill + jnz FillLoop +FillDone: + pop di ;restore caller's register variables + pop si + 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 modified Listing 40.1 to employ the vertical-monotone detection test diff --git a/41-04.md b/41-04.md index 4cf07b0..96e73c5 100644 --- a/41-04.md +++ b/41-04.md @@ -12,330 +12,334 @@ pages: 766-771 **LISTING 41.4 L41-4.C** - /* Color-fills an arbitrarily-shaped polygon described by VertexList. - If the first and last points in VertexList are not the same, the path - around the polygon is automatically closed. All vertices are offset - by (XOffset, YOffset). Returns 1 for success, 0 if memory allocation - failed. All C code tested with Borland C++. +```c +/* Color-fills an arbitrarily-shaped polygon described by VertexList. +If the first and last points in VertexList are not the same, the path +around the polygon is automatically closed. All vertices are offset +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 - 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, - 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. +If the polygon shape is known in advance, speedier processing may be +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, +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. - Define CONVEX_CODE_LINKED if the fast convex polygon filling code from - the February 1991 column is linked in. Otherwise, convex polygons are - handled by the complex polygon filling code. - Nonconvex is handled as complex in this implementation. See text for a - discussion of faster nonconvex handling. */ +Define CONVEX_CODE_LINKED if the fast convex polygon filling code from +the February 1991 column is linked in. Otherwise, convex polygons are +handled by the complex polygon filling code. +Nonconvex is handled as complex in this implementation. See text for a +discussion of faster nonconvex handling. */ - #include - #include - #ifdef __TURBOC__ - #include - #else /* MSC */ - #include - #endif - #include "polygon.h" +#include +#include +#ifdef __TURBOC__ +#include +#else /* MSC */ +#include +#endif +#include "polygon.h" - #define SWAP(a,b) {temp = a; a = b; b = temp;} +#define SWAP(a,b) {temp = a; a = b; b = temp;} - struct EdgeState { - struct EdgeState *NextEdge; - int X; - int StartY; - int WholePixelXMove; - int XDirection; - int ErrorTerm; - int ErrorTermAdjUp; - int ErrorTermAdjDown; - int Count; - }; +struct EdgeState { + struct EdgeState *NextEdge; + int X; + int StartY; + int WholePixelXMove; + int XDirection; + int ErrorTerm; + int ErrorTermAdjUp; + int ErrorTermAdjDown; + int Count; +}; - extern void DrawHorizontalLineSeg(int, int, int, int); - extern int FillMonotoneVerticalPolygon(struct PointListHeader *, - int, int, int); - extern int PolygonIsMonotoneVertical(struct PointListHeader *); - static void BuildGET(struct PointListHeader *, struct EdgeState *, - int, int); - static void MoveXSortedToAET(int); - static void ScanOutAET(int, int); - static void AdvanceAET(void); - static void XSortAET(void); +extern void DrawHorizontalLineSeg(int, int, int, int); +extern int FillMonotoneVerticalPolygon(struct PointListHeader *, + int, int, int); +extern int PolygonIsMonotoneVertical(struct PointListHeader *); +static void BuildGET(struct PointListHeader *, struct EdgeState *, + int, int); +static void MoveXSortedToAET(int); +static void ScanOutAET(int, int); +static void AdvanceAET(void); +static void XSortAET(void); - /* Pointers to global edge table (GET) and active edge table (AET) */ - static struct EdgeState *GETPtr, *AETPtr; +/* Pointers to global edge table (GET) and active edge table (AET) */ +static struct EdgeState *GETPtr, *AETPtr; - int FillPolygon(struct PointListHeader * VertexList, int Color, - int PolygonShape, int XOffset, int YOffset) - { - struct EdgeState *EdgeTableBuffer; - int CurrentY; +int FillPolygon(struct PointListHeader * VertexList, int Color, + int PolygonShape, int XOffset, int YOffset) +{ + struct EdgeState *EdgeTableBuffer; + int CurrentY; - #ifdef CONVEX_CODE_LINKED - /* Pass convex polygons through to fast convex polygon filler */ - if ((PolygonShape == CONVEX) || - PolygonIsMonotoneVertical(VertexList)) - return(FillMonotoneVerticalPolygon(VertexList, Color, XOffset, - YOffset)); - #endif +#ifdef CONVEX_CODE_LINKED + /* Pass convex polygons through to fast convex polygon filler */ + if ((PolygonShape == CONVEX) || + PolygonIsMonotoneVertical(VertexList)) + return(FillMonotoneVerticalPolygon(VertexList, Color, XOffset, + YOffset)); +#endif - /* It takes a minimum of 3 vertices to cause any pixels to be - drawn; reject polygons that are guaranteed to be invisible */ - if (VertexList->Length < 3) - return(1); - /* Get enough memory to store the entire edge table */ - if ((EdgeTableBuffer = - (struct EdgeState *) (malloc(sizeof(struct EdgeState) * - VertexList->Length))) == NULL) - 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, - so long as at least one edge remains in either the GET or AET */ - AETPtr = NULL; /* initialize the active edge table to empty */ - CurrentY = GETPtr->StartY; /* start at the top polygon vertex */ - while ((GETPtr != NULL) || (AETPtr != NULL)) { - MoveXSortedToAET(CurrentY); /* update AET for this scan line */ - ScanOutAET(CurrentY, Color); /* draw this scan line from AET */ - AdvanceAET(); /* advance AET edges 1 scan line */ - XSortAET(); /* resort on X */ - CurrentY++; /* advance to the next scan line */ - } - /* Release the memory we've allocated and we're done */ - free(EdgeTableBuffer); - return(1); - } + /* It takes a minimum of 3 vertices to cause any pixels to be + drawn; reject polygons that are guaranteed to be invisible */ + if (VertexList->Length < 3) + return(1); + /* Get enough memory to store the entire edge table */ + if ((EdgeTableBuffer = + (struct EdgeState *) (malloc(sizeof(struct EdgeState) * + VertexList->Length))) == NULL) + 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, + so long as at least one edge remains in either the GET or AET */ + AETPtr = NULL; /* initialize the active edge table to empty */ + CurrentY = GETPtr->StartY; /* start at the top polygon vertex */ + while ((GETPtr != NULL) || (AETPtr != NULL)) { + MoveXSortedToAET(CurrentY); /* update AET for this scan line */ + ScanOutAET(CurrentY, Color); /* draw this scan line from AET */ + AdvanceAET(); /* advance AET edges 1 scan line */ + XSortAET(); /* resort on X */ + CurrentY++; /* advance to the next scan line */ + } + /* Release the memory we've allocated and we're done */ + free(EdgeTableBuffer); + return(1); +} - /* Creates a GET in the buffer pointed to by NextFreeEdgeStruc from - the vertex list. Edge endpoints are flipped, if necessary, to - guarantee all edges go top to bottom. The GET is sorted primarily - by ascending Y start coordinate, and secondarily by ascending X - start coordinate within edges with common Y coordinates. */ - static void BuildGET(struct PointListHeader * VertexList, - struct EdgeState * NextFreeEdgeStruc, int XOffset, int YOffset) - { - int i, StartX, StartY, EndX, EndY, DeltaY, DeltaX, Width, temp; - struct EdgeState *NewEdgePtr; - struct EdgeState *FollowingEdge, **FollowingEdgeLink; - struct Point *VertexPtr; +/* Creates a GET in the buffer pointed to by NextFreeEdgeStruc from + the vertex list. Edge endpoints are flipped, if necessary, to + guarantee all edges go top to bottom. The GET is sorted primarily + by ascending Y start coordinate, and secondarily by ascending X + start coordinate within edges with common Y coordinates. */ +static void BuildGET(struct PointListHeader * VertexList, + struct EdgeState * NextFreeEdgeStruc, int XOffset, int YOffset) +{ + int i, StartX, StartY, EndX, EndY, DeltaY, DeltaX, Width, temp; + struct EdgeState *NewEdgePtr; + struct EdgeState *FollowingEdge, **FollowingEdgeLink; + struct Point *VertexPtr; - /* Scan through the vertex list and put all non-0-height edges into - the GET, sorted by increasing Y start coordinate */ - VertexPtr = VertexList->PointPtr; /* point to the vertex list */ - GETPtr = NULL; /* initialize the global edge table to empty */ - for (i = 0; i < VertexList->Length; i++) { - /* Calculate the edge height and width */ - StartX = VertexPtr[i].X + XOffset; - StartY = VertexPtr[i].Y + YOffset; - /* The edge runs from the current point to the previous one */ - if (i == 0) { - /* Wrap back around to the end of the list */ - EndX = VertexPtr[VertexList->Length-1].X + XOffset; - EndY = VertexPtr[VertexList->Length-1].Y + YOffset; - } else { - EndX = VertexPtr[i-1].X + XOffset; - EndY = VertexPtr[i-1].Y + YOffset; - } - /* Make sure the edge runs top to bottom */ - if (StartY > EndY) { - SWAP(StartX, EndX); - SWAP(StartY, EndY); - } - /* 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 - structure */ - NewEdgePtr = NextFreeEdgeStruc++; - NewEdgePtr->XDirection = /* direction in which X moves */ - ((DeltaX = EndX - StartX) > 0) ? 1 : -1; - Width = abs(DeltaX); - NewEdgePtr->X = StartX; - NewEdgePtr->StartY = StartY; - NewEdgePtr->Count = DeltaY; - NewEdgePtr->ErrorTermAdjDown = DeltaY; - if (DeltaX >= 0) /* initial error term going L->R */ - NewEdgePtr->ErrorTerm = 0; - else /* initial error term going R->L */ - NewEdgePtr->ErrorTerm = -DeltaY + 1; - if (DeltaY >= Width) { /* Y-major edge */ - NewEdgePtr->WholePixelXMove = 0; - NewEdgePtr->ErrorTermAdjUp = Width; - } else { /* X-major edge */ - NewEdgePtr->WholePixelXMove = - (Width / DeltaY) * NewEdgePtr->XDirection; - NewEdgePtr->ErrorTermAdjUp = Width % DeltaY; - } - /* Link the new edge into the GET so that the edge list is - still sorted by Y coordinate, and by X coordinate for all - edges with the same Y coordinate */ - FollowingEdgeLink = &GETPtr; - for (;;) { - FollowingEdge = *FollowingEdgeLink; - if ((FollowingEdge == NULL) || - (FollowingEdge->StartY > StartY) || - ((FollowingEdge->StartY == StartY) && - (FollowingEdge->X >= StartX))) { - NewEdgePtr->NextEdge = FollowingEdge; - *FollowingEdgeLink = NewEdgePtr; - break; - } - FollowingEdgeLink = &FollowingEdge->NextEdge; - } - } - } - } + /* Scan through the vertex list and put all non-0-height edges into + the GET, sorted by increasing Y start coordinate */ + VertexPtr = VertexList->PointPtr; /* point to the vertex list */ + GETPtr = NULL; /* initialize the global edge table to empty */ + for (i = 0; i < VertexList->Length; i++) { + /* Calculate the edge height and width */ + StartX = VertexPtr[i].X + XOffset; + StartY = VertexPtr[i].Y + YOffset; + /* The edge runs from the current point to the previous one */ + if (i == 0) { + /* Wrap back around to the end of the list */ + EndX = VertexPtr[VertexList->Length-1].X + XOffset; + EndY = VertexPtr[VertexList->Length-1].Y + YOffset; + } else { + EndX = VertexPtr[i-1].X + XOffset; + EndY = VertexPtr[i-1].Y + YOffset; + } + /* Make sure the edge runs top to bottom */ + if (StartY > EndY) { + SWAP(StartX, EndX); + SWAP(StartY, EndY); + } + /* 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 + structure */ + NewEdgePtr = NextFreeEdgeStruc++; + NewEdgePtr->XDirection = /* direction in which X moves */ + ((DeltaX = EndX - StartX) > 0) ? 1 : -1; + Width = abs(DeltaX); + NewEdgePtr->X = StartX; + NewEdgePtr->StartY = StartY; + NewEdgePtr->Count = DeltaY; + NewEdgePtr->ErrorTermAdjDown = DeltaY; + if (DeltaX >= 0) /* initial error term going L->R */ + NewEdgePtr->ErrorTerm = 0; + else /* initial error term going R->L */ + NewEdgePtr->ErrorTerm = -DeltaY + 1; + if (DeltaY >= Width) { /* Y-major edge */ + NewEdgePtr->WholePixelXMove = 0; + NewEdgePtr->ErrorTermAdjUp = Width; + } else { /* X-major edge */ + NewEdgePtr->WholePixelXMove = + (Width / DeltaY) * NewEdgePtr->XDirection; + NewEdgePtr->ErrorTermAdjUp = Width % DeltaY; + } + /* Link the new edge into the GET so that the edge list is + still sorted by Y coordinate, and by X coordinate for all + edges with the same Y coordinate */ + FollowingEdgeLink = &GETPtr; + for (;;) { + FollowingEdge = *FollowingEdgeLink; + if ((FollowingEdge == NULL) || + (FollowingEdge->StartY > StartY) || + ((FollowingEdge->StartY == StartY) && + (FollowingEdge->X >= StartX))) { + NewEdgePtr->NextEdge = FollowingEdge; + *FollowingEdgeLink = NewEdgePtr; + break; + } + FollowingEdgeLink = &FollowingEdge->NextEdge; + } + } + } +} - /* Sorts all edges currently in the active edge table into ascending - order of current X coordinates */ - static void XSortAET() { - struct EdgeState *CurrentEdge, **CurrentEdgePtr, *TempEdge; - int SwapOccurred; +/* Sorts all edges currently in the active edge table into ascending + order of current X coordinates */ +static void XSortAET() { + struct EdgeState *CurrentEdge, **CurrentEdgePtr, *TempEdge; + int SwapOccurred; - /* Scan through the AET and swap any adjacent edges for which the - second edge is at a lower current X coord than the first edge. - Repeat until no further swapping is needed */ - if (AETPtr != NULL) { - do { - SwapOccurred = 0; - CurrentEdgePtr = &AETPtr; - while ((CurrentEdge = *CurrentEdgePtr)->NextEdge != NULL) { - if (CurrentEdge->X > CurrentEdge->NextEdge->X) { - /* The second edge has a lower X than the first; - swap them in the AET */ - TempEdge = CurrentEdge->NextEdge->NextEdge; - *CurrentEdgePtr = CurrentEdge->NextEdge; - CurrentEdge->NextEdge->NextEdge = CurrentEdge; - CurrentEdge->NextEdge = TempEdge; - SwapOccurred = 1; - } - CurrentEdgePtr = &(*CurrentEdgePtr)->NextEdge; - } - } while (SwapOccurred != 0); - } - } + /* Scan through the AET and swap any adjacent edges for which the + second edge is at a lower current X coord than the first edge. + Repeat until no further swapping is needed */ + if (AETPtr != NULL) { + do { + SwapOccurred = 0; + CurrentEdgePtr = &AETPtr; + while ((CurrentEdge = *CurrentEdgePtr)->NextEdge != NULL) { + if (CurrentEdge->X > CurrentEdge->NextEdge->X) { + /* The second edge has a lower X than the first; + swap them in the AET */ + TempEdge = CurrentEdge->NextEdge->NextEdge; + *CurrentEdgePtr = CurrentEdge->NextEdge; + CurrentEdge->NextEdge->NextEdge = CurrentEdge; + CurrentEdge->NextEdge = TempEdge; + SwapOccurred = 1; + } + CurrentEdgePtr = &(*CurrentEdgePtr)->NextEdge; + } + } while (SwapOccurred != 0); + } +} - /* Advances each edge in the AET by one scan line. - Removes edges that have been fully scanned. */ - static void AdvanceAET() { - struct EdgeState *CurrentEdge, **CurrentEdgePtr; +/* Advances each edge in the AET by one scan line. + Removes edges that have been fully scanned. */ +static void AdvanceAET() { + struct EdgeState *CurrentEdge, **CurrentEdgePtr; - /* Count down and remove or advance each edge in the AET */ - CurrentEdgePtr = &AETPtr; - while ((CurrentEdge = *CurrentEdgePtr) != NULL) { - /* Count off one scan line for this edge */ - if ((--(CurrentEdge->Count)) == 0) { - /* This edge is finished, so remove it from the AET */ - *CurrentEdgePtr = CurrentEdge->NextEdge; - } else { - /* Advance the edge's X coordinate by minimum move */ - CurrentEdge->X += CurrentEdge->WholePixelXMove; - /* Determine whether it's time for X to advance one extra */ - if ((CurrentEdge->ErrorTerm += - CurrentEdge->ErrorTermAdjUp) > 0) { - CurrentEdge->X += CurrentEdge->XDirection; - CurrentEdge->ErrorTerm -= CurrentEdge->ErrorTermAdjDown; - } - CurrentEdgePtr = &CurrentEdge->NextEdge; - } - } - } + /* Count down and remove or advance each edge in the AET */ + CurrentEdgePtr = &AETPtr; + while ((CurrentEdge = *CurrentEdgePtr) != NULL) { + /* Count off one scan line for this edge */ + if ((--(CurrentEdge->Count)) == 0) { + /* This edge is finished, so remove it from the AET */ + *CurrentEdgePtr = CurrentEdge->NextEdge; + } else { + /* Advance the edge's X coordinate by minimum move */ + CurrentEdge->X += CurrentEdge->WholePixelXMove; + /* Determine whether it's time for X to advance one extra */ + if ((CurrentEdge->ErrorTerm += + CurrentEdge->ErrorTermAdjUp) > 0) { + CurrentEdge->X += CurrentEdge->XDirection; + CurrentEdge->ErrorTerm -= CurrentEdge->ErrorTermAdjDown; + } + CurrentEdgePtr = &CurrentEdge->NextEdge; + } + } +} - /* Moves all edges that start at the specified Y coordinate from the - GET to the AET, maintaining the X sorting of the AET. */ - static void MoveXSortedToAET(int YToMove) { - struct EdgeState *AETEdge, **AETEdgePtr, *TempEdge; - int CurrentX; +/* Moves all edges that start at the specified Y coordinate from the + GET to the AET, maintaining the X sorting of the AET. */ +static void MoveXSortedToAET(int YToMove) { + struct EdgeState *AETEdge, **AETEdgePtr, *TempEdge; + 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 - 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 - guaranteed to belong later in the AET than the one just added. */ - AETEdgePtr = &AETPtr; - while ((GETPtr != NULL) && (GETPtr->StartY == YToMove)) { - CurrentX = GETPtr->X; - /* Link the new edge into the AET so that the AET is still - sorted by X coordinate */ - for (;;) { - AETEdge = *AETEdgePtr; - if ((AETEdge == NULL) || (AETEdge->X >= CurrentX)) { - TempEdge = GETPtr->NextEdge; - *AETEdgePtr = GETPtr; /* link the edge into the AET */ - GETPtr->NextEdge = AETEdge; - AETEdgePtr = &GETPtr->NextEdge; - GETPtr = TempEdge; /* unlink the edge from the GET */ - break; - } else { - AETEdgePtr = &AETEdge->NextEdge; - } - } - } - } + /* 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 + 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 + guaranteed to belong later in the AET than the one just added. */ + AETEdgePtr = &AETPtr; + while ((GETPtr != NULL) && (GETPtr->StartY == YToMove)) { + CurrentX = GETPtr->X; + /* Link the new edge into the AET so that the AET is still + sorted by X coordinate */ + for (;;) { + AETEdge = *AETEdgePtr; + if ((AETEdge == NULL) || (AETEdge->X >= CurrentX)) { + TempEdge = GETPtr->NextEdge; + *AETEdgePtr = GETPtr; /* link the edge into the AET */ + GETPtr->NextEdge = AETEdge; + AETEdgePtr = &GETPtr->NextEdge; + GETPtr = TempEdge; /* unlink the edge from the GET */ + break; + } else { + AETEdgePtr = &AETEdge->NextEdge; + } + } + } +} - /* Fills the scan line described by the current AET at the specified Y - coordinate in the specified color, using the odd/even fill rule */ - static void ScanOutAET(int YToScan, int Color) { - int LeftX; - struct EdgeState *CurrentEdge; +/* Fills the scan line described by the current AET at the specified Y + coordinate in the specified color, using the odd/even fill rule */ +static void ScanOutAET(int YToScan, int Color) { + int LeftX; + struct EdgeState *CurrentEdge; - /* Scan through the AET, drawing line segments as each pair of edge - crossings is encountered. The nearest pixel on or to the right - of left edges is drawn, and the nearest pixel to the left of but - not on right edges is drawn */ - CurrentEdge = AETPtr; - while (CurrentEdge != NULL) { - LeftX = CurrentEdge->X; - CurrentEdge = CurrentEdge->NextEdge; - DrawHorizontalLineSeg(YToScan, LeftX, CurrentEdge->X-1, Color); - CurrentEdge = CurrentEdge->NextEdge; - } - } + /* Scan through the AET, drawing line segments as each pair of edge + crossings is encountered. The nearest pixel on or to the right + of left edges is drawn, and the nearest pixel to the left of but + not on right edges is drawn */ + CurrentEdge = AETPtr; + while (CurrentEdge != NULL) { + LeftX = CurrentEdge->X; + CurrentEdge = CurrentEdge->NextEdge; + DrawHorizontalLineSeg(YToScan, LeftX, CurrentEdge->X-1, Color); + CurrentEdge = CurrentEdge->NextEdge; + } +} +``` **LISTING 41.5 POLYGON.H** - /* Header file for polygon-filling code */ +```c +/* Header file for polygon-filling code */ - #define CONVEX 0 - #define NONCONVEX 1 - #define COMPLEX 2 +#define CONVEX 0 +#define NONCONVEX 1 +#define COMPLEX 2 - /* Describes a single point (used for a single vertex) */ - struct Point { - int X; /* X coordinate */ - int Y; /* Y coordinate */ - }; +/* Describes a single point (used for a single vertex) */ +struct Point { + int X; /* X coordinate */ + int Y; /* Y coordinate */ +}; - /* Describes series of points (used to store a list of vertices that describe - a polygon; each vertex is assumed to connect to the two adjacent vertices, and - last vertex is assumed to connect to the first) */ - struct PointListHeader { - int Length; /* # of points */ - struct Point * PointPtr; /* pointer to list of points */ - }; +/* Describes series of points (used to store a list of vertices that describe +a polygon; each vertex is assumed to connect to the two adjacent vertices, and +last vertex is assumed to connect to the first) */ +struct PointListHeader { + int Length; /* # of points */ + struct Point * PointPtr; /* pointer to list of points */ +}; - /* Describes beginning and ending X coordinates of a single horizontal line */ - struct HLine { - int XStart; /* X coordinate of leftmost pixel in line */ - int XEnd; /* X coordinate of rightmost pixel in line */ - }; +/* Describes beginning and ending X coordinates of a single horizontal line */ +struct HLine { + int XStart; /* X coordinate of leftmost pixel in line */ + int XEnd; /* X coordinate of rightmost pixel in line */ +}; - /* Describes a Length-long series of horizontal lines, all assumed to be on - contiguous scan lines starting at YStart and proceeding downward (used to - describe scan-converted polygon to low-level hardware-dependent drawing code) */ - struct HLineList { - int Length; /* # of horizontal lines */ - int YStart; /* Y coordinate of topmost line */ - struct HLine * HLinePtr; /* pointer to list of horz lines */ - }; +/* Describes a Length-long series of horizontal lines, all assumed to be on +contiguous scan lines starting at YStart and proceeding downward (used to +describe scan-converted polygon to low-level hardware-dependent drawing code) */ +struct HLineList { + int Length; /* # of horizontal lines */ + int YStart; /* Y coordinate of topmost line */ + struct HLine * HLinePtr; /* pointer to list of horz lines */ +}; - /* Describes a color as an RGB triple, plus one byte for other info */ - struct RGB { unsigned char Red, Green, Blue, Spare; }; +/* Describes a color as an RGB triple, plus one byte for other info */ +struct RGB { unsigned char Red, Green, Blue, Spare; }; +``` Is monotone-vertical polygon detection worth all this trouble? Under the right circumstances, you bet. In a situation where a great many polygons diff --git a/42-02.md b/42-02.md index 578be42..f66dfe5 100644 --- a/42-02.md +++ b/42-02.md @@ -76,137 +76,139 @@ loop unrolling. Make no mistake about it, Wu antialiasing is fast. **LISTING 42.1 L42-1.C** - /* Function to draw an antialiased line from (X0,Y0) to (X1,Y1), using an - * antialiasing approach published by Xiaolin Wu in the July 1991 issue of - * Computer Graphics. Requires that the palette be set up so that there - * are NumLevels intensity levels of the desired drawing color, starting at - * color BaseColor (100% intensity) and followed by (NumLevels-1) levels of - * evenly decreasing intensity, with color (BaseColor+NumLevels-1) being 0% - * intensity of the desired drawing color (black). This code is suitable for - * use at screen resolutions, with lines typically no more than 1K long; for - * longer lines, 32-bit error arithmetic must be used to avoid problems with - * fixed-point inaccuracy. No clipping is performed in DrawWuLine; it must be - * performed either at a higher level or in the DrawPixel function. - * Tested with Borland C++ in C compilation mode and the small model. - */ - extern void DrawPixel(int, int, int); +```c +/* Function to draw an antialiased line from (X0,Y0) to (X1,Y1), using an + * antialiasing approach published by Xiaolin Wu in the July 1991 issue of + * Computer Graphics. Requires that the palette be set up so that there + * are NumLevels intensity levels of the desired drawing color, starting at + * color BaseColor (100% intensity) and followed by (NumLevels-1) levels of + * evenly decreasing intensity, with color (BaseColor+NumLevels-1) being 0% + * intensity of the desired drawing color (black). This code is suitable for + * use at screen resolutions, with lines typically no more than 1K long; for + * longer lines, 32-bit error arithmetic must be used to avoid problems with + * fixed-point inaccuracy. No clipping is performed in DrawWuLine; it must be + * performed either at a higher level or in the DrawPixel function. + * Tested with Borland C++ in C compilation mode and the small model. + */ +extern void DrawPixel(int, int, int); - /* Wu antialiased line drawer. - * (X0,Y0),(X1,Y1) = line to draw - * BaseColor = color # of first color in block used for antialiasing, the - * 100% intensity version of the drawing color - * NumLevels = size of color block, with BaseColor+NumLevels-1 being the - * 0% intensity version of the drawing color - * IntensityBits = log base 2 of NumLevels; the # of bits used to describe - * the intensity of the drawing color. 2**IntensityBits==NumLevels - */ - void DrawWuLine(int X0, int Y0, int X1, int Y1, int BaseColor, int NumLevels, - unsigned int IntensityBits) - { - unsigned int IntensityShift, ErrorAdj, ErrorAcc; - unsigned int ErrorAccTemp, Weighting, WeightingComplementMask; - int DeltaX, DeltaY, Temp, XDir; +/* Wu antialiased line drawer. + * (X0,Y0),(X1,Y1) = line to draw + * BaseColor = color # of first color in block used for antialiasing, the + * 100% intensity version of the drawing color + * NumLevels = size of color block, with BaseColor+NumLevels-1 being the + * 0% intensity version of the drawing color + * IntensityBits = log base 2 of NumLevels; the # of bits used to describe + * the intensity of the drawing color. 2**IntensityBits==NumLevels + */ +void DrawWuLine(int X0, int Y0, int X1, int Y1, int BaseColor, int NumLevels, + unsigned int IntensityBits) +{ + unsigned int IntensityShift, ErrorAdj, ErrorAcc; + unsigned int ErrorAccTemp, Weighting, WeightingComplementMask; + int DeltaX, DeltaY, Temp, XDir; - /* Make sure the line runs top to bottom */ - if (Y0 > Y1) { - Temp = Y0; Y0 = Y1; Y1 = Temp; - Temp = X0; X0 = X1; X1 = Temp; - } - /* Draw the initial pixel, which is always exactly intersected by - the line and so needs no weighting */ - DrawPixel(X0, Y0, BaseColor); + /* Make sure the line runs top to bottom */ + if (Y0 > Y1) { + Temp = Y0; Y0 = Y1; Y1 = Temp; + Temp = X0; X0 = X1; X1 = Temp; + } + /* Draw the initial pixel, which is always exactly intersected by + the line and so needs no weighting */ + DrawPixel(X0, Y0, BaseColor); - if ((DeltaX = X1 - X0) >= 0) { - XDir = 1; - } else { - XDir = -1; - DeltaX = -DeltaX; /* make DeltaX positive */ - } - /* Special-case horizontal, vertical, and diagonal lines, which - require no weighting because they go right through the center of - every pixel */ - if ((DeltaY = Y1 - Y0) == 0) { - /* Horizontal line */ - while (DeltaX-- != 0) { - X0 += XDir; - DrawPixel(X0, Y0, BaseColor); - } - return; - } - if (DeltaX == 0) { - /* Vertical line */ - do { - Y0++; - DrawPixel(X0, Y0, BaseColor); - } while (--DeltaY != 0); - return; - } - if (DeltaX == DeltaY) { - /* Diagonal line */ - do { - X0 += XDir; - Y0++; - DrawPixel(X0, Y0, BaseColor); - } while (--DeltaY != 0); - return; - } - /* line is not horizontal, diagonal, or vertical */ - ErrorAcc = 0; /* initialize the line error accumulator to 0 */ - /* # of bits by which to shift ErrorAcc to get intensity level */ - IntensityShift = 16 - IntensityBits; - /* Mask used to flip all bits in an intensity weighting, producing the - result (1 - intensity weighting) */ - WeightingComplementMask = NumLevels - 1; - /* Is this an X-major or Y-major line? */ - if (DeltaY > DeltaX) { - /* Y-major line; calculate 16-bit fixed-point fractional part of a - pixel that X advances each time Y advances 1 pixel, truncating the - result so that we won't overrun the endpoint along the X axis */ - ErrorAdj = ((unsigned long) DeltaX << 16) / (unsigned long) DeltaY; - /* Draw all pixels other than the first and last */ - while (--DeltaY) { - ErrorAccTemp = ErrorAcc; /* remember currrent accumulated error */ - ErrorAcc += ErrorAdj; /* calculate error for next pixel */ - if (ErrorAcc <= ErrorAccTemp) { - /* The error accumulator turned over, so advance the X coord */ - X0 += XDir; - } - Y0++; /* Y-major, so always advance Y */ - /* The IntensityBits most significant bits of ErrorAcc give us the - intensity weighting for this pixel, and the complement of the - weighting for the paired pixel */ - Weighting = ErrorAcc >> IntensityShift; - DrawPixel(X0, Y0, BaseColor + Weighting); - DrawPixel(X0 + XDir, Y0, - BaseColor + (Weighting ^ WeightingComplementMask)); - } - /* Draw the final pixel, which is always exactly intersected by the line - and so needs no weighting */ - DrawPixel(X1, Y1, BaseColor); - return; - } - /* It's an X-major line; calculate 16-bit fixed-point fractional part of a - pixel that Y advances each time X advances 1 pixel, truncating the - result to avoid overrunning the endpoint along the X axis */ - ErrorAdj = ((unsigned long) DeltaY << 16) / (unsigned long) DeltaX; - /* Draw all pixels other than the first and last */ - while (--DeltaX) { - ErrorAccTemp = ErrorAcc; /* remember currrent accumulated error */ - ErrorAcc += ErrorAdj; /* calculate error for next pixel */ - if (ErrorAcc <= ErrorAccTemp) { - /* The error accumulator turned over, so advance the Y coord */ - Y0++; - } - X0 += XDir; /* X-major, so always advance X */ - /* The IntensityBits most significant bits of ErrorAcc give us the - intensity weighting for this pixel, and the complement of the - weighting for the paired pixel */ - Weighting = ErrorAcc >> IntensityShift; - DrawPixel(X0, Y0, BaseColor + Weighting); - DrawPixel(X0, Y0 + 1, - BaseColor + (Weighting ^ WeightingComplementMask)); - } - /* Draw the final pixel, which is always exactly intersected by the line - and so needs no weighting */ - DrawPixel(X1, Y1, BaseColor); - } + if ((DeltaX = X1 - X0) >= 0) { + XDir = 1; + } else { + XDir = -1; + DeltaX = -DeltaX; /* make DeltaX positive */ + } + /* Special-case horizontal, vertical, and diagonal lines, which + require no weighting because they go right through the center of + every pixel */ + if ((DeltaY = Y1 - Y0) == 0) { + /* Horizontal line */ + while (DeltaX-- != 0) { + X0 += XDir; + DrawPixel(X0, Y0, BaseColor); + } + return; + } + if (DeltaX == 0) { + /* Vertical line */ + do { + Y0++; + DrawPixel(X0, Y0, BaseColor); + } while (--DeltaY != 0); + return; + } + if (DeltaX == DeltaY) { + /* Diagonal line */ + do { + X0 += XDir; + Y0++; + DrawPixel(X0, Y0, BaseColor); + } while (--DeltaY != 0); + return; + } + /* line is not horizontal, diagonal, or vertical */ + ErrorAcc = 0; /* initialize the line error accumulator to 0 */ + /* # of bits by which to shift ErrorAcc to get intensity level */ + IntensityShift = 16 - IntensityBits; + /* Mask used to flip all bits in an intensity weighting, producing the + result (1 - intensity weighting) */ + WeightingComplementMask = NumLevels - 1; + /* Is this an X-major or Y-major line? */ + if (DeltaY > DeltaX) { + /* Y-major line; calculate 16-bit fixed-point fractional part of a + pixel that X advances each time Y advances 1 pixel, truncating the + result so that we won't overrun the endpoint along the X axis */ + ErrorAdj = ((unsigned long) DeltaX << 16) / (unsigned long) DeltaY; + /* Draw all pixels other than the first and last */ + while (--DeltaY) { + ErrorAccTemp = ErrorAcc; /* remember currrent accumulated error */ + ErrorAcc += ErrorAdj; /* calculate error for next pixel */ + if (ErrorAcc <= ErrorAccTemp) { + /* The error accumulator turned over, so advance the X coord */ + X0 += XDir; + } + Y0++; /* Y-major, so always advance Y */ + /* The IntensityBits most significant bits of ErrorAcc give us the + intensity weighting for this pixel, and the complement of the + weighting for the paired pixel */ + Weighting = ErrorAcc >> IntensityShift; + DrawPixel(X0, Y0, BaseColor + Weighting); + DrawPixel(X0 + XDir, Y0, + BaseColor + (Weighting ^ WeightingComplementMask)); + } + /* Draw the final pixel, which is always exactly intersected by the line + and so needs no weighting */ + DrawPixel(X1, Y1, BaseColor); + return; + } + /* It's an X-major line; calculate 16-bit fixed-point fractional part of a + pixel that Y advances each time X advances 1 pixel, truncating the + result to avoid overrunning the endpoint along the X axis */ + ErrorAdj = ((unsigned long) DeltaY << 16) / (unsigned long) DeltaX; + /* Draw all pixels other than the first and last */ + while (--DeltaX) { + ErrorAccTemp = ErrorAcc; /* remember currrent accumulated error */ + ErrorAcc += ErrorAdj; /* calculate error for next pixel */ + if (ErrorAcc <= ErrorAccTemp) { + /* The error accumulator turned over, so advance the Y coord */ + Y0++; + } + X0 += XDir; /* X-major, so always advance X */ + /* The IntensityBits most significant bits of ErrorAcc give us the + intensity weighting for this pixel, and the complement of the + weighting for the paired pixel */ + Weighting = ErrorAcc >> IntensityShift; + DrawPixel(X0, Y0, BaseColor + Weighting); + DrawPixel(X0, Y0 + 1, + BaseColor + (Weighting ^ WeightingComplementMask)); + } + /* Draw the final pixel, which is always exactly intersected by the line + and so needs no weighting */ + DrawPixel(X1, Y1, BaseColor); +} +``` diff --git a/42-03.md b/42-03.md index bd73dbb..a6dac1e 100644 --- a/42-03.md +++ b/42-03.md @@ -24,160 +24,164 @@ both Wu-antialiased and non-antialiased lines. **LISTING 42.2 L42-2.C** - /* Sample line-drawing program to demonstrate Wu antialiasing. Also draws - * non-antialiased lines for comparison. - * Tested with Borland C++ in C compilation mode and the small model. - */ - #include - #include +```c +/* Sample line-drawing program to demonstrate Wu antialiasing. Also draws + * non-antialiased lines for comparison. + * Tested with Borland C++ in C compilation mode and the small model. + */ +#include +#include - void SetPalette(struct WuColor *); - extern void DrawWuLine(int, int, int, int, int, int, unsigned int); - extern void DrawLine(int, int, int, int, int); - extern void SetMode(void); - extern int ScreenWidthInPixels; /* screen dimension globals */ - extern int ScreenHeightInPixels; +void SetPalette(struct WuColor *); +extern void DrawWuLine(int, int, int, int, int, int, unsigned int); +extern void DrawLine(int, int, int, int, int); +extern void SetMode(void); +extern int ScreenWidthInPixels; /* screen dimension globals */ +extern int ScreenHeightInPixels; - #define NUM_WU_COLORS 2 /* # of colors we'll do antialiased drawing with */ - struct WuColor { /* describes one color used for antialiasing */ - int BaseColor; /* # of start of palette intensity block in DAC */ - int NumLevels; /* # of intensity levels */ - int IntensityBits; /* IntensityBits == log2 NumLevels */ - int MaxRed; /* red component of color at full intensity */ - int MaxGreen; /* green component of color at full intensity */ - int MaxBlue; /* blue component of color at full intensity */ - }; - enum {WU_BLUE=0, WU_WHITE=1}; /* drawing colors */ - struct WuColor WuColors[NUM_WU_COLORS] = /* blue and white */ - {{192, 32, 5, 0, 0, 0x3F}, {224, 32, 5, 0x3F, 0x3F, 0x3F}}; +#define NUM_WU_COLORS 2 /* # of colors we'll do antialiased drawing with */ +struct WuColor { /* describes one color used for antialiasing */ + int BaseColor; /* # of start of palette intensity block in DAC */ + int NumLevels; /* # of intensity levels */ + int IntensityBits; /* IntensityBits == log2 NumLevels */ + int MaxRed; /* red component of color at full intensity */ + int MaxGreen; /* green component of color at full intensity */ + int MaxBlue; /* blue component of color at full intensity */ +}; +enum {WU_BLUE=0, WU_WHITE=1}; /* drawing colors */ +struct WuColor WuColors[NUM_WU_COLORS] = /* blue and white */ + {{192, 32, 5, 0, 0, 0x3F}, {224, 32, 5, 0x3F, 0x3F, 0x3F}}; - void main() - { - int CurrentColor, i; - union REGS regset; +void main() +{ + int CurrentColor, i; + union REGS regset; - /* Draw Wu-antialiased lines in all directions */ - SetMode(); - SetPalette(WuColors); - for (i=5; i +```c +/* VGA mode 13h pixel-drawing and mode set functions. + * Tested with Borland C++ in C compilation mode and the small model. + */ +#include - /* Screen dimension globals, used in main program to scale. */ - int ScreenWidthInPixels = 320; - int ScreenHeightInPixels = 200; +/* Screen dimension globals, used in main program to scale. */ +int ScreenWidthInPixels = 320; +int ScreenHeightInPixels = 200; - /* Mode 13h draw pixel function. */ - void DrawPixel(int X, int Y, int Color) - { - #define SCREEN_SEGMENT 0xA000 - unsigned char far *ScreenPtr; +/* Mode 13h draw pixel function. */ +void DrawPixel(int X, int Y, int Color) +{ +#define SCREEN_SEGMENT 0xA000 + unsigned char far *ScreenPtr; - FP_SEG(ScreenPtr) = SCREEN_SEGMENT; - FP_OFF(ScreenPtr) = (unsigned int) Y * ScreenWidthInPixels + X; - *ScreenPtr = Color; - } + FP_SEG(ScreenPtr) = SCREEN_SEGMENT; + FP_OFF(ScreenPtr) = (unsigned int) Y * ScreenWidthInPixels + X; + *ScreenPtr = Color; +} - /* Mode 13h mode-set function. */ - void SetMode() - { - union REGS regset; +/* Mode 13h mode-set function. */ +void SetMode() +{ + union REGS regset; - /* Set to 320x200 256-color graphics mode */ - regset.x.ax = 0x0013; - int86(0x10, ®set, ®set); - } + /* Set to 320x200 256-color graphics mode */ + regset.x.ax = 0x0013; + int86(0x10, ®set, ®set); +} +``` diff --git a/42-04.md b/42-04.md index 807ebab..76a5e80 100644 --- a/42-04.md +++ b/42-04.md @@ -12,72 +12,74 @@ pages: 784-787 **LISTING 42.4 L42-4.C** - /* Function to draw a non-antialiased line from (X0,Y0) to (X1,Y1), using a - * simple fixed-point error accumulation approach. - * Tested with Borland C++ in C compilation mode and the small model. - */ - extern void DrawPixel(int, int, int); +```c +/* Function to draw a non-antialiased line from (X0,Y0) to (X1,Y1), using a + * simple fixed-point error accumulation approach. + * Tested with Borland C++ in C compilation mode and the small model. + */ +extern void DrawPixel(int, int, int); - /* Non-antialiased line drawer. - * (X0,Y0),(X1,Y1) = line to draw, Color = color in which to draw - */ - void DrawLine(int X0, int Y0, int X1, int Y1, int Color) - { - unsigned long ErrorAcc, ErrorAdj; - int DeltaX, DeltaY, XDir, Temp; +/* Non-antialiased line drawer. + * (X0,Y0),(X1,Y1) = line to draw, Color = color in which to draw + */ +void DrawLine(int X0, int Y0, int X1, int Y1, int Color) +{ + unsigned long ErrorAcc, ErrorAdj; + int DeltaX, DeltaY, XDir, Temp; - /* Make sure the line runs top to bottom */ - if (Y0 > Y1) { - Temp = Y0; Y0 = Y1; Y1 = Temp; - Temp = X0; X0 = X1; X1 = Temp; - } - DrawPixel(X0, Y0, Color); /* draw the initial pixel */ - if ((DeltaX = X1 - X0) >= 0) { - XDir = 1; - } else { - XDir = -1; - DeltaX = -DeltaX; /* make DeltaX positive */ - } - if ((DeltaY = Y1 - Y0) == 0) /* done if only one point in the line */ - if (DeltaX == 0) return; + /* Make sure the line runs top to bottom */ + if (Y0 > Y1) { + Temp = Y0; Y0 = Y1; Y1 = Temp; + Temp = X0; X0 = X1; X1 = Temp; + } + DrawPixel(X0, Y0, Color); /* draw the initial pixel */ + if ((DeltaX = X1 - X0) >= 0) { + XDir = 1; + } else { + XDir = -1; + DeltaX = -DeltaX; /* make DeltaX positive */ + } + if ((DeltaY = Y1 - Y0) == 0) /* done if only one point in the line */ + if (DeltaX == 0) return; - ErrorAcc = 0x8000; /* initialize line error accumulator to .5, so we can - advance when we get halfway to the next pixel */ - /* Is this an X-major or Y-major line? */ - if (DeltaY > DeltaX) { - /* Y-major line; calculate 16-bit fixed-point fractional part of a - pixel that X advances each time Y advances 1 pixel */ - ErrorAdj = ((((unsigned long)DeltaX << 17) / (unsigned long)DeltaY) + - 1) >> 1; - /* Draw all pixels between the first and last */ - do { - ErrorAcc += ErrorAdj; /* calculate error for this pixel */ - if (ErrorAcc & ~0xFFFFL) { - /* The error accumulator turned over, so advance the X coord */ - X0 += XDir; - ErrorAcc &= 0xFFFFL; /* clear integer part of result */ - } - Y0++; /* Y-major, so always advance Y */ - DrawPixel(X0, Y0, Color); - } while (--DeltaY); - return; - } - /* It's an X-major line; calculate 16-bit fixed-point fractional part of a - pixel that Y advances each time X advances 1 pixel */ - ErrorAdj = ((((unsigned long)DeltaY << 17) / (unsigned long)DeltaX) + - 1) >> 1; - /* Draw all remaining pixels */ - do { - ErrorAcc += ErrorAdj; /* calculate error for this pixel */ - if (ErrorAcc & ~0xFFFFL) { - /* The error accumulator turned over, so advance the Y coord */ - Y0++; - ErrorAcc &= 0xFFFFL; /* clear integer part of result */ - } - X0 += XDir; /* X-major, so always advance X */ - DrawPixel(X0, Y0, Color); - } while (--DeltaX); - } + ErrorAcc = 0x8000; /* initialize line error accumulator to .5, so we can + advance when we get halfway to the next pixel */ + /* Is this an X-major or Y-major line? */ + if (DeltaY > DeltaX) { + /* Y-major line; calculate 16-bit fixed-point fractional part of a + pixel that X advances each time Y advances 1 pixel */ + ErrorAdj = ((((unsigned long)DeltaX << 17) / (unsigned long)DeltaY) + + 1) >> 1; + /* Draw all pixels between the first and last */ + do { + ErrorAcc += ErrorAdj; /* calculate error for this pixel */ + if (ErrorAcc & ~0xFFFFL) { + /* The error accumulator turned over, so advance the X coord */ + X0 += XDir; + ErrorAcc &= 0xFFFFL; /* clear integer part of result */ + } + Y0++; /* Y-major, so always advance Y */ + DrawPixel(X0, Y0, Color); + } while (--DeltaY); + return; + } + /* It's an X-major line; calculate 16-bit fixed-point fractional part of a + pixel that Y advances each time X advances 1 pixel */ + ErrorAdj = ((((unsigned long)DeltaY << 17) / (unsigned long)DeltaX) + + 1) >> 1; + /* Draw all remaining pixels */ + do { + ErrorAcc += ErrorAdj; /* calculate error for this pixel */ + if (ErrorAcc & ~0xFFFFL) { + /* The error accumulator turned over, so advance the Y coord */ + Y0++; + ErrorAcc &= 0xFFFFL; /* clear integer part of result */ + } + X0 += XDir; /* X-major, so always advance X */ + DrawPixel(X0, Y0, Color); + } while (--DeltaX); +} +``` Listing 42.1 isn't particularly fast, because it calls **DrawPixel()** for each pixel. On the other hand, **DrawPixel()** makes it easy to try @@ -95,46 +97,48 @@ drawn with a ruler. **LISTING 42.5 L42-5.C** - /* Mode set and pixel-drawing functions for the 640x480 256-color mode of - * Tseng Labs ET4000-based SuperVGAs. - * Tested with Borland C++ in C compilation mode and the small model. - */ - #include +```c +/* Mode set and pixel-drawing functions for the 640x480 256-color mode of + * Tseng Labs ET4000-based SuperVGAs. + * Tested with Borland C++ in C compilation mode and the small model. + */ +#include - /* Screen dimension globals, used in main program to scale */ - int ScreenWidthInPixels = 640; - int ScreenHeightInPixels = 480; +/* Screen dimension globals, used in main program to scale */ +int ScreenWidthInPixels = 640; +int ScreenHeightInPixels = 480; - /* ET4000 640x480 256-color draw pixel function. */ - void DrawPixel(int X, int Y, int Color) - { - #define SCREEN_SEGMENT 0xA000 - #define GC_SEGMENT_SELECT 0x3CD /* ET4000 segment (bank) select reg */ - unsigned char far *ScreenPtr; - unsigned int Bank; - unsigned long BitmapAddress; +/* ET4000 640x480 256-color draw pixel function. */ +void DrawPixel(int X, int Y, int Color) +{ +#define SCREEN_SEGMENT 0xA000 +#define GC_SEGMENT_SELECT 0x3CD /* ET4000 segment (bank) select reg */ + unsigned char far *ScreenPtr; + unsigned int Bank; + unsigned long BitmapAddress; - /* full bitmap address of pixel, as measured from address 0 to 0xFFFFF */ - BitmapAddress = (unsigned long) Y * ScreenWidthInPixels + X; - /* Bank # is upper word of bitmap addr */ - Bank = BitmapAddress >> 16; - /* Upper nibble is read bank #, lower nibble is write bank # */ - outp(GC_SEGMENT_SELECT, (Bank << 4) | Bank); - /* Draw into the bank */ - FP_SEG(ScreenPtr) = SCREEN_SEGMENT; - FP_OFF(ScreenPtr) = (unsigned int) BitmapAddress; - *ScreenPtr = Color; - } + /* full bitmap address of pixel, as measured from address 0 to 0xFFFFF */ + BitmapAddress = (unsigned long) Y * ScreenWidthInPixels + X; + /* Bank # is upper word of bitmap addr */ + Bank = BitmapAddress >> 16; + /* Upper nibble is read bank #, lower nibble is write bank # */ + outp(GC_SEGMENT_SELECT, (Bank << 4) | Bank); + /* Draw into the bank */ + FP_SEG(ScreenPtr) = SCREEN_SEGMENT; + FP_OFF(ScreenPtr) = (unsigned int) BitmapAddress; + *ScreenPtr = Color; +} - /* ET4000 640x480 256-color mode-set function. */ - void SetMode() - { - union REGS regset; +/* ET4000 640x480 256-color mode-set function. */ +void SetMode() +{ + union REGS regset; - /* Set to 640x480 256-color graphics mode */ - regset.x.ax = 0x002E; - int86(0x10, ®set, ®set); - } + /* Set to 640x480 256-color graphics mode */ + regset.x.ax = 0x002E; + int86(0x10, ®set, ®set); +} +``` Listing 42.1 requires that the DAC palette be set up so that a **NumLevel**-long block of palette entries contains linearly decreasing diff --git a/42-05.md b/42-05.md index e8a3789..745f114 100644 --- a/42-05.md +++ b/42-05.md @@ -12,275 +12,277 @@ pages: 787-792 **LISTING 42.6 L42-6.ASM** - ; C near-callable function to draw an antialiased line from - ; (X0,Y0) to (X1,Y1), in mode 13h, the VGA's standard 320x200 256-color - ; mode. Uses an antialiasing approach published by Xiaolin Wu in the July - ; 1991 issue of Computer Graphics. Requires that the palette be set up so - ; that there are NumLevels intensity levels of the desired drawing color, - ; starting at color BaseColor (100% intensity) and followed by (NumLevels-1) - ; levels of evenly decreasing intensity, with color (BaseColor+NumLevels-1) - ; being 0% intensity of the desired drawing color (black). No clipping is - ; performed in DrawWuLine. Handles a maximum of 256 intensity levels per - ; antialiased color. This code is suitable for use at screen resolutions, - ; with lines typically no more than 1K long; for longer lines, 32-bit error - ; arithmetic must be used to avoid problems with fixed-point inaccuracy. - ; Tested with TASM. - ; - ; C near-callable as: - ; void DrawWuLine(int X0, int Y0, int X1, int Y1, int BaseColor, - ; int NumLevels, unsigned int IntensityBits); +```nasm +; C near-callable function to draw an antialiased line from +; (X0,Y0) to (X1,Y1), in mode 13h, the VGA's standard 320x200 256-color +; mode. Uses an antialiasing approach published by Xiaolin Wu in the July +; 1991 issue of Computer Graphics. Requires that the palette be set up so +; that there are NumLevels intensity levels of the desired drawing color, +; starting at color BaseColor (100% intensity) and followed by (NumLevels-1) +; levels of evenly decreasing intensity, with color (BaseColor+NumLevels-1) +; being 0% intensity of the desired drawing color (black). No clipping is +; performed in DrawWuLine. Handles a maximum of 256 intensity levels per +; antialiased color. This code is suitable for use at screen resolutions, +; with lines typically no more than 1K long; for longer lines, 32-bit error +; arithmetic must be used to avoid problems with fixed-point inaccuracy. +; Tested with TASM. +; +; C near-callable as: +; void DrawWuLine(int X0, int Y0, int X1, int Y1, int BaseColor, +; int NumLevels, unsigned int IntensityBits); - SCREEN_WIDTH_IN_BYTES equ 320;# of bytes from the start of one scan line - ; to the start of the next - SCREEN_SEGMENT equ 0a000h;segment in which screen memory resides +SCREEN_WIDTH_IN_BYTES equ 320;# of bytes from the start of one scan line +; to the start of the next +SCREEN_SEGMENT equ 0a000h;segment in which screen memory resides - ; Parameters passed in stack frame. - parms struc - dw 2 dup (?) ;pushed BP and return address - X0 dw ? ;X coordinate of line start point - Y0 dw ? ;Y coordinate of line start point - X1 dw ? ;X coordinate of line end point - Y1 dw ? ;Y coordinate of line end point - BaseColor dw ? ;color # of first color in block used for - ;antialiasing, the 100% intensity version of the - ;drawing color - NumLevels dw ? ;size of color block, with BaseColor+NumLevels-1 - ; being the 0% intensity version of the drawing color - ; (maximum NumLevels = 256) - IntensityBits dw ? ;log base 2 of NumLevels; the # of bits used to - ; describe the intensity of the drawing color. - ; 2**IntensityBits==NumLevels - ; (maximum IntensityBits = 8) - parms ends +; Parameters passed in stack frame. +parms struc + dw 2 dup (?) ;pushed BP and return address +X0 dw ? ;X coordinate of line start point +Y0 dw ? ;Y coordinate of line start point +X1 dw ? ;X coordinate of line end point +Y1 dw ? ;Y coordinate of line end point +BaseColor dw ? ;color # of first color in block used for + ;antialiasing, the 100% intensity version of the + ;drawing color +NumLevels dw ? ;size of color block, with BaseColor+NumLevels-1 + ; being the 0% intensity version of the drawing color + ; (maximum NumLevels = 256) +IntensityBits dw ? ;log base 2 of NumLevels; the # of bits used to + ; describe the intensity of the drawing color. + ; 2**IntensityBits==NumLevels + ; (maximum IntensityBits = 8) +parms ends - .model small - .code - ; Screen dimension globals, used in main program to scale. - _ScreenWidthInPixels dw 320 - _ScreenHeightInPixels dw 200 +.model small +.code +; Screen dimension globals, used in main program to scale. +_ScreenWidthInPixels dw 320 +_ScreenHeightInPixels dw 200 - .code - public _DrawWuLine - _DrawWuLine proc near - push bp ;preserve caller's stack frame - mov bp,sp ;point to local stack frame - push si ;preserve C's register variables - push di - push ds ;preserve C's default data segment - cld ;make string instructions increment their pointers + .code + public _DrawWuLine +_DrawWuLine proc near + push bp ;preserve caller's stack frame + mov bp,sp ;point to local stack frame + push si ;preserve C's register variables + push di + push ds ;preserve C's default data segment + cld ;make string instructions increment their pointers - ; Make sure the line runs top to bottom. - mov si,[bp].X0 - mov ax,[bp].Y0 - cmp ax,[bp].Y1 ;swap endpoints if necessary to ensure that - jna NoSwap ; Y0 <= Y1 - xchg [bp].Y1,ax - mov [bp].Y0,ax - xchg [bp].X1,si - mov [bp].X0,si - NoSwap: +; Make sure the line runs top to bottom. + mov si,[bp].X0 + mov ax,[bp].Y0 + cmp ax,[bp].Y1 ;swap endpoints if necessary to ensure that + jna NoSwap ; Y0 <= Y1 + xchg [bp].Y1,ax + mov [bp].Y0,ax + xchg [bp].X1,si + mov [bp].X0,si +NoSwap: - ; Draw the initial pixel, which is always exactly intersected by the line - ; and so needs no weighting. - mov dx,SCREEN_SEGMENT - mov ds,dx ;point DS to the screen segment - mov dx,SCREEN_WIDTH_IN_BYTES - mul dx ;Y0 * SCREEN_WIDTH_IN_BYTES yields the offset - ; of the start of the row start the initial - ; pixel is on - add si,ax ;point DS:SI to the initial pixel - mov al,byte ptr [bp].BaseColor ;color with which to draw - mov [si],al ;draw the initial pixel +; Draw the initial pixel, which is always exactly intersected by the line +; and so needs no weighting. + mov dx,SCREEN_SEGMENT + mov ds,dx ;point DS to the screen segment + mov dx,SCREEN_WIDTH_IN_BYTES + mul dx ;Y0 * SCREEN_WIDTH_IN_BYTES yields the offset + ; of the start of the row start the initial + ; pixel is on + add si,ax ;point DS:SI to the initial pixel + mov al,byte ptr [bp].BaseColor ;color with which to draw + mov [si],al ;draw the initial pixel - mov bx,1 ;XDir = 1; assume DeltaX >= 0 - mov cx,[bp].X1 - sub cx,[bp].X0 ;DeltaX; is it >= 1? - jns DeltaXSet ;yes, move left->right, all set - ;no, move right->left - neg cx ;make DeltaX positive - neg bx ;XDir = -1 - DeltaXSet: + mov bx,1 ;XDir = 1; assume DeltaX >= 0 + mov cx,[bp].X1 + sub cx,[bp].X0 ;DeltaX; is it >= 1? + jns DeltaXSet ;yes, move left->right, all set + ;no, move right->left + neg cx ;make DeltaX positive + neg bx ;XDir = -1 +DeltaXSet: - ; Special-case horizontal, vertical, and diagonal lines, which require no - ; weighting because they go right through the center of every pixel. - mov dx,[bp].Y1 - sub dx,[bp].Y0 ;DeltaY; is it 0? - jnz NotHorz ;no, not horizontal - ;yes, is horizontal, special case - and bx,bx ;draw from left->right? - jns DoHorz ;yes, all set - std ;no, draw right->left - DoHorz: - lea di,[bx+si] ;point DI to next pixel to draw - mov ax,ds - mov es,ax ;point ES:DI to next pixel to draw - mov al,byte ptr [bp].BaseColor ;color with which to draw - ;CX = DeltaX at this point - rep stosb ;draw the rest of the horizontal line - cld ;restore default direction flag - jmp Done ;and we're done +; Special-case horizontal, vertical, and diagonal lines, which require no +; weighting because they go right through the center of every pixel. + mov dx,[bp].Y1 + sub dx,[bp].Y0 ;DeltaY; is it 0? + jnz NotHorz ;no, not horizontal + ;yes, is horizontal, special case + and bx,bx ;draw from left->right? + jns DoHorz ;yes, all set + std ;no, draw right->left +DoHorz: + lea di,[bx+si] ;point DI to next pixel to draw + mov ax,ds + mov es,ax ;point ES:DI to next pixel to draw + mov al,byte ptr [bp].BaseColor ;color with which to draw + ;CX = DeltaX at this point + rep stosb ;draw the rest of the horizontal line + cld ;restore default direction flag + jmp Done ;and we're done - align2 - NotHorz: - and cx,cx ;is DeltaX 0? - jnz NotVert ;no, not a vertical line - ;yes, is vertical, special case - mov al,byte ptr [bp].BaseColor ;color with which to draw - VertLoop: - add si,SCREEN_WIDTH_IN_BYTES ;point to next pixel to draw - mov [si],al ;draw the next pixel - dec dx ;--DeltaY - jnz VertLoop - jmp Done ;and we're done - - align2 - NotVert: - cmp cx,dx ;DeltaX == DeltaY? - jnz NotDiag ;no, not diagonal - ;yes, is diagonal, special case - mov al,byte ptr [bp].BaseColor ;color with which to draw - DiagLoop: - lea si,[si+SCREEN_WIDTH_IN_BYTES+bx] - ;advance to next pixel to draw by - ; incrementing Y and adding XDir to X - mov [si],al ;draw the next pixel - dec dx ;--DeltaY - jnz DiagLoop - jmp Done ;and we're done + align2 +NotHorz: + and cx,cx ;is DeltaX 0? + jnz NotVert ;no, not a vertical line + ;yes, is vertical, special case + mov al,byte ptr [bp].BaseColor ;color with which to draw +VertLoop: + add si,SCREEN_WIDTH_IN_BYTES ;point to next pixel to draw + mov [si],al ;draw the next pixel + dec dx ;--DeltaY + jnz VertLoop + jmp Done ;and we're done + + align2 +NotVert: + cmp cx,dx ;DeltaX == DeltaY? + jnz NotDiag ;no, not diagonal + ;yes, is diagonal, special case + mov al,byte ptr [bp].BaseColor ;color with which to draw +DiagLoop: + lea si,[si+SCREEN_WIDTH_IN_BYTES+bx] + ;advance to next pixel to draw by + ; incrementing Y and adding XDir to X + mov [si],al ;draw the next pixel + dec dx ;--DeltaY + jnz DiagLoop + jmp Done ;and we're done - ; Line is not horizontal, diagonal, or vertical. - align2 - NotDiag: - ; Is this an X-major or Y-major line? - cmp dx,cx - jbX Major ;it's X-major +; Line is not horizontal, diagonal, or vertical. + align2 +NotDiag: +; Is this an X-major or Y-major line? + cmp dx,cx + jbX Major ;it's X-major - ; It's a Y-major line. Calculate the 16-bit fixed-point fractional part of a - ; pixel that X advances each time Y advances 1 pixel, truncating the result - ; to avoid overrunning the endpoint along the X axis. - xchg dx,cx ;DX = DeltaX, CX = DeltaY - sub ax,ax ;make DeltaX 16.16 fixed-point value in DX:AX - div cx ;AX = (DeltaX << 16) / DeltaY. Won't overflow - ; because DeltaX < DeltaY - mov di,cx ;DI = DeltaY (loop count) - sub si,bx ;back up the start X by 1, as explained below - mov dx,-1 ;initialize the line error accumulator to -1, - ; so that it will turn over immediately and - ; advance X to the start X. This is necessary - ; properly to bias error sums of 0 to mean - ; "advance next time" rather than "advance - ; this time," so that the final error sum can - ; never cause drawing to overrun the final X - ; coordinate (works in conjunction with - ; truncating ErrorAdj, to make sure X can't - ; overrun) - mov cx,8 ;CL = # of bits by which to shift - sub cx,[bp].IntensityBits ; ErrorAcc to get intensity level (8 - ; instead of 16 because we work only - ; with the high byte of ErrorAcc) - mov ch,byte ptr [bp].NumLevels ;mask used to flip all bits in an - dec ch ; intensity weighting, producing - ; result (1 - intensity weighting) - mov bp,BaseColor[bp] ;***stack frame not available*** - ;***from now on *** - xchg bp,ax ;BP = ErrorAdj, AL = BaseColor, - ; AH = scratch register +; It's a Y-major line. Calculate the 16-bit fixed-point fractional part of a +; pixel that X advances each time Y advances 1 pixel, truncating the result +; to avoid overrunning the endpoint along the X axis. + xchg dx,cx ;DX = DeltaX, CX = DeltaY + sub ax,ax ;make DeltaX 16.16 fixed-point value in DX:AX + div cx ;AX = (DeltaX << 16) / DeltaY. Won't overflow + ; because DeltaX < DeltaY + mov di,cx ;DI = DeltaY (loop count) + sub si,bx ;back up the start X by 1, as explained below + mov dx,-1 ;initialize the line error accumulator to -1, + ; so that it will turn over immediately and + ; advance X to the start X. This is necessary + ; properly to bias error sums of 0 to mean + ; "advance next time" rather than "advance + ; this time," so that the final error sum can + ; never cause drawing to overrun the final X + ; coordinate (works in conjunction with + ; truncating ErrorAdj, to make sure X can't + ; overrun) + mov cx,8 ;CL = # of bits by which to shift + sub cx,[bp].IntensityBits ; ErrorAcc to get intensity level (8 + ; instead of 16 because we work only + ; with the high byte of ErrorAcc) + mov ch,byte ptr [bp].NumLevels ;mask used to flip all bits in an + dec ch ; intensity weighting, producing + ; result (1 - intensity weighting) + mov bp,BaseColor[bp] ;***stack frame not available*** + ;***from now on *** + xchg bp,ax ;BP = ErrorAdj, AL = BaseColor, + ; AH = scratch register - ; Draw all remaining pixels. - YMajorLoop: - add dx,bp ;calculate error for next pixel - jnc NoXAdvance ;not time to step in X yet - ;the error accumulator turned over, - ;so advance the X coord - add si,bx ;add XDir to the pixel pointer - NoXAdvance: - add si,SCREEN_WIDTH_IN_BYTES ;Y-major, so always advance Y +; Draw all remaining pixels. +YMajorLoop: + add dx,bp ;calculate error for next pixel + jnc NoXAdvance ;not time to step in X yet + ;the error accumulator turned over, + ;so advance the X coord + add si,bx ;add XDir to the pixel pointer +NoXAdvance: + add si,SCREEN_WIDTH_IN_BYTES ;Y-major, so always advance Y - ; The IntensityBits most significant bits of ErrorAcc give us the intensity - ; weighting for this pixel, and the complement of the weighting for the - ; paired pixel. - mov ah,dh ;msb of ErrorAcc - shr ah,cl ;Weighting = ErrorAcc >> IntensityShift; - add ah,al ;BaseColor + Weighting - mov [si],ah ;DrawPixel(X, Y, BaseColor + Weighting); - mov ah,dh ;msb of ErrorAcc - shr ah,cl ;Weighting = ErrorAcc >> IntensityShift; - xor ah,ch ;Weighting ^ WeightingComplementMask - add ah,al ;BaseColor + (Weighting ^ WeightingComplementMask) - mov [si+bx],ah ;DrawPixel(X+XDir, Y, - ; BaseColor + (Weighting ^ WeightingComplementMask)); - dec di ;--DeltaY - jnz YMajorLoop - jmp Done ;we're done with this line +; The IntensityBits most significant bits of ErrorAcc give us the intensity +; weighting for this pixel, and the complement of the weighting for the +; paired pixel. + mov ah,dh ;msb of ErrorAcc + shr ah,cl ;Weighting = ErrorAcc >> IntensityShift; + add ah,al ;BaseColor + Weighting + mov [si],ah ;DrawPixel(X, Y, BaseColor + Weighting); + mov ah,dh ;msb of ErrorAcc + shr ah,cl ;Weighting = ErrorAcc >> IntensityShift; + xor ah,ch ;Weighting ^ WeightingComplementMask + add ah,al ;BaseColor + (Weighting ^ WeightingComplementMask) + mov [si+bx],ah ;DrawPixel(X+XDir, Y, + ; BaseColor + (Weighting ^ WeightingComplementMask)); + dec di ;--DeltaY + jnz YMajorLoop + jmp Done ;we're done with this line - ; It's an X-major line. - align2 - XMajor: - ; Calculate the 16-bit fixed-point fractional part of a pixel that Y advances - ; each time X advances 1 pixel, truncating the result to avoid overrunning - ; the endpoint along the X axis. - sub ax,ax ;make DeltaY 16.16 fixed-point value in DX:AX - div cx ;AX = (DeltaY << 16) / Deltax. Won't overflow - ; because DeltaY < DeltaX - mov di,cx ;DI = DeltaX (loop count) - sub si,SCREEN_WIDTH_IN_BYTES ;back up the start X by 1, as - ; explained below - mov dx,-1 ;initialize the line error accumulator to -1, - ; so that it will turn over immediately and - ; advance Y to the start Y. This is necessary - ; properly to bias error sums of 0 to mean - ; "advance next time" rather than "advance - ; this time," so that the final error sum can - ; never cause drawing to overrun the final Y - ; coordinate (works in conjunction with - ; truncating ErrorAdj, to make sure Y can't - ; overrun) - mov cx,8 ;CL = # of bits by which to shift - sub cx,[bp].IntensityBits ; ErrorAcc to get intensity level (8 - ; instead of 16 because we work only - ; with the high byte of ErrorAcc) - mov ch,byte ptr [bp].NumLevels ;mask used to flip all bits in an - dec ch ; intensity weighting, producing - ; result (1 - intensity weighting) - mov bp,BaseColor[bp];***stack frame not available*** - ;***from now on *** - xchg bp,ax ;BP = ErrorAdj, AL = BaseColor, - ; AH = scratch register - ; Draw all remaining pixels. - XMajorLoop: - add dx,bp ;calculate error for next pixel - jnc NoYAdvance ;not time to step in Y yet - ;the error accumulator turned over, - ; so advance the Y coord - add si,SCREEN_WIDTH_IN_BYTES ;advance Y - NoYAdvance: - add si,bx ;X-major, so add XDir to the pixel pointer +; It's an X-major line. + align2 +XMajor: +; Calculate the 16-bit fixed-point fractional part of a pixel that Y advances +; each time X advances 1 pixel, truncating the result to avoid overrunning +; the endpoint along the X axis. + sub ax,ax ;make DeltaY 16.16 fixed-point value in DX:AX + div cx ;AX = (DeltaY << 16) / Deltax. Won't overflow + ; because DeltaY < DeltaX + mov di,cx ;DI = DeltaX (loop count) + sub si,SCREEN_WIDTH_IN_BYTES ;back up the start X by 1, as + ; explained below + mov dx,-1 ;initialize the line error accumulator to -1, + ; so that it will turn over immediately and + ; advance Y to the start Y. This is necessary + ; properly to bias error sums of 0 to mean + ; "advance next time" rather than "advance + ; this time," so that the final error sum can + ; never cause drawing to overrun the final Y + ; coordinate (works in conjunction with + ; truncating ErrorAdj, to make sure Y can't + ; overrun) + mov cx,8 ;CL = # of bits by which to shift + sub cx,[bp].IntensityBits ; ErrorAcc to get intensity level (8 + ; instead of 16 because we work only + ; with the high byte of ErrorAcc) + mov ch,byte ptr [bp].NumLevels ;mask used to flip all bits in an + dec ch ; intensity weighting, producing + ; result (1 - intensity weighting) + mov bp,BaseColor[bp];***stack frame not available*** + ;***from now on *** + xchg bp,ax ;BP = ErrorAdj, AL = BaseColor, + ; AH = scratch register + ; Draw all remaining pixels. +XMajorLoop: + add dx,bp ;calculate error for next pixel + jnc NoYAdvance ;not time to step in Y yet + ;the error accumulator turned over, + ; so advance the Y coord + add si,SCREEN_WIDTH_IN_BYTES ;advance Y +NoYAdvance: + add si,bx ;X-major, so add XDir to the pixel pointer - ; The IntensityBits most significant bits of ErrorAcc give us the intensity - ; weighting for this pixel, and the complement of the weighting for the - ; paired pixel. - mov ah,dh ;msb of ErrorAcc - shr ah,cl ;Weighting = ErrorAcc >> IntensityShift; - add ah,al ;BaseColor + Weighting - mov [si],ah ;DrawPixel(X, Y, BaseColor + Weighting); - mov ah,dh ;msb of ErrorAcc - shr ah,cl ;Weighting = ErrorAcc >> IntensityShift; - xor ah,ch ;Weighting ^ WeightingComplementMask - add ah,al ;BaseColor + (Weighting ^ WeightingComplementMask) - mov [si+SCREEN_WIDTH_IN_BYTES],ah - ;DrawPixel(X, Y+SCREEN_WIDTH_IN_BYTES, - ; BaseColor + (Weighting ^ WeightingComplementMask)); - dec di ;--DeltaX - jnz XMajorLoop - - Done: ;we're done with this line - pop ds ;restore C's default data segment - pop di ;restore C's register variables - pop si - pop bp ;restore caller's stack frame - ret ;done - _DrawWuLine endp - end +; The IntensityBits most significant bits of ErrorAcc give us the intensity +; weighting for this pixel, and the complement of the weighting for the +; paired pixel. + mov ah,dh ;msb of ErrorAcc + shr ah,cl ;Weighting = ErrorAcc >> IntensityShift; + add ah,al ;BaseColor + Weighting + mov [si],ah ;DrawPixel(X, Y, BaseColor + Weighting); + mov ah,dh ;msb of ErrorAcc + shr ah,cl ;Weighting = ErrorAcc >> IntensityShift; + xor ah,ch ;Weighting ^ WeightingComplementMask + add ah,al ;BaseColor + (Weighting ^ WeightingComplementMask) + mov [si+SCREEN_WIDTH_IN_BYTES],ah + ;DrawPixel(X, Y+SCREEN_WIDTH_IN_BYTES, + ; BaseColor + (Weighting ^ WeightingComplementMask)); + dec di ;--DeltaX + jnz XMajorLoop + +Done: ;we're done with this line + pop ds ;restore C's default data segment + pop di ;restore C's register variables + pop si + pop bp ;restore caller's stack frame + ret ;done +_DrawWuLine endp + end +``` #### Notes on Wu Antialiasing {#Heading6} diff --git a/43-03.md b/43-03.md index 3e04e5c..811fda9 100644 --- a/43-03.md +++ b/43-03.md @@ -12,498 +12,500 @@ pages: 801-810 **LISTING 43.1 L43-1.ASM** - ; Program to demonstrate bit-plane animation. Performs - ; flicker-free animation with image transparency and - ; image precedence across four distinct planes, with - ; 13 32x32 images kept in motion at once. - ; - ; - ; Set to higher values to slow down on faster computers. - ; 0 is fine for a PC. 500 is a reasonable setting for an AT. - ; Slowing animation further allows a good look at - ; transparency and the lack of flicker and color effects - ; when images cross. - ; - SLOWDOWN equ 10000 - ; - ; Plane selects for the four colors we're using. - ; - RED equ 01h - GREEN equ 02h - BLUE equ 04h - WHITE equ 08h - ; - VGA_SEGMENT equ 0a000h ;mode 10h display memory - ; segment - SC_INDEX equ 3c4h ;Sequence Controller Index - ; register - MAP_MASK equ 2 ;Map Mask register index in - ; Sequence Controller - 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 - ; handle word outs to - ; indexed VGA regs - ; - stack segment para stack ‘STACK' - db 512 dup (?) - stack ends - ; - ; Complete info about one object that we're animating. - ; - ObjectStructure struc - Delay dw ? ;used to delay for n passes - ; throught the loop to - ; control animation speed - BaseDelay dw ? ;reset value for Delay - Image dw ? ;pointer to drawing info - ; for object - XCoord dw ? ;object X location in pixels - XInc dw ? ;# of pixels to increment - ; location by in the X - ; direction on each move - XLeftLimit dw ? ;left limit of X motion - XRightLimit dw ? ;right limit of X motion - YCoord dw ? ;object Y location in pixels - YInc dw ? ;# of pixels to increment - ; location by in the Y - ; direction on each move - YTopLimit dw ? ;top limit of Y motion - YBottomLimit dw ? ;bottom limit of Y motion - PlaneSelect db ? ;mask to select plane to - ; which object is drawn - db ? ;to make an even # of words - ; long, for better 286 - ; performance (keeps the - ; following structure - ; word-aligned) - ObjectStructure ends - ; - 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 - ; obscured by any other plane), while plane 0 has the - ; highest precedence (displays in front of any other plane). - ; - Colors db 000h ;background color=black - db 03ch ;plane 0 only=red - db 03ah ;plane 1 only=green - db 03ch ;planes 0&1=red (plane 0 priority) - db 039h ;plane 2 only=blue - db 03ch ;planes 0&2=red (plane 0 priority) - db 03ah ;planes 1&2=green (plane 1 priority) - db 03ch ;planes 0&1&2=red (plane 0 priority) - db 03fh ;plane 3 only=white - db 03ch ;planes 0&3=red (plane 0 priority) - db 03ah ;planes 1&3=green (plane 1 priority) - db 03ch ;planes 0&1&3=red (plane 0 priority) - db 039h ;planes 2&3=blue (plane 2 priority) - db 03ch ;planes 0&2&3=red (plane 0 priority) - db 03ah ;planes 1&2&3=green (plane 1 priority) - db 03ch ;planes 0&1&2&3=red (plane 0 priority) - db 000h ;border color=black - ; - ; Image of a hollow square. - ; 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. - ; - Square label byte - dw 48,6 ;height in pixels, width in bytes - rept 8 - db 0,0,0,0,0,0;top blank border - endm - .radix 2 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,00000000,00000000,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - db 0,11111111,11111111,11111111,11111111,0 - .radix 10 - rept 8 - db 0,0,0,0,0,0;bottom blank border - endm - ; - ; Image of a hollow diamond with a smaller diamond in the - ; middle. - ; 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. - ; - Diamond label byte - dw 48,6 ;height in pixels, width in bytes - rept 8 - db 0,0,0,0,0,0;top blank border - endm - .radix 2 - db 0,00000000,00000001,10000000,00000000,0 - db 0,00000000,00000011,11000000,00000000,0 - db 0,00000000,00000111,11100000,00000000,0 - db 0,00000000,00001111,11110000,00000000,0 - db 0,00000000,00011111,11111000,00000000,0 - db 0,00000000,00111110,01111100,00000000,0 - db 0,00000000,01111100,00111110,00000000,0 - db 0,00000000,11111000,00011111,00000000,0 - db 0,00000001,11110000,00001111,10000000,0 - db 0,00000011,11100000,00000111,11000000,0 - db 0,00000111,11000000,00000011,11100000,0 - db 0,00001111,10000001,10000001,11110000,0 - db 0,00011111,00000011,11000000,11111000,0 - db 0,00111110,00000111,11100000,01111100,0 - db 0,01111100,00001111,11110000,00111110,0 - db 0,11111000,00011111,11111000,00011111,0 - db 0,11111000,00011111,11111000,00011111,0 - db 0,01111100,00001111,11110000,00111110,0 - db 0,00111110,00000111,11100000,01111100,0 - db 0,00011111,00000011,11000000,11111000,0 - db 0,00001111,10000001,10000001,11110000,0 - db 0,00000111,11000000,00000011,11100000,0 - db 0,00000011,11100000,00000111,11000000,0 - db 0,00000001,11110000,00001111,10000000,0 - db 0,00000000,11111000,00011111,00000000,0 - db 0,00000000,01111100,00111110,00000000,0 - db 0,00000000,00111110,01111100,00000000,0 - db 0,00000000,00011111,11111000,00000000,0 - db 0,00000000,00001111,11110000,00000000,0 - db 0,00000000,00000111,11100000,00000000,0 - db 0,00000000,00000011,11000000,00000000,0 - db 0,00000000,00000001,10000000,00000000,0 - .radix 10 - rept 8 - db 0,0,0,0,0,0; bottom blank border - endm - ; - ; List of objects to animate. - ; - even;word-align for better 286 performance - ; - ObjectListlabelObjectStructure - ObjectStructure <1,21,Diamond,88,8,80,512,16,0,0,350,RED> - ObjectStructure <1,15,Square,296,8,112,480,144,0,0,350,RED> - ObjectStructure <1,23,Diamond,88,8,80,512,256,0,0,350,RED> - ObjectStructure <1,13,Square,120,0,0,640,144,4,0,280,BLUE> - ObjectStructure <1,11,Diamond,208,0,0,640,144,4,0,280,BLUE> - ObjectStructure <1,8,Square,296,0,0,640,144,4,0,288,BLUE> - ObjectStructure <1,9,Diamond,384,0,0,640,144,4,0,288,BLUE> - ObjectStructure <1,14,Square,472,0,0,640,144,4,0,280,BLUE> - ObjectStructure <1,8,Diamond,200,8,0,576,48,6,0,280,GREEN> - ObjectStructure <1,8,Square,248,8,0,576,96,6,0,280,GREEN> - ObjectStructure <1,8,Diamond,296,8,0,576,144,6,0,280,GREEN> - ObjectStructure <1,8,Square,344,8,0,576,192,6,0,280,GREEN> - ObjectStructure <1,8,Diamond,392,8,0,576,240,6,0,280,GREEN> - ObjectListEndlabelObjectStructure - ; - Dataends - ; - ; Macro to output a word value to a port. - ; - OUT_WORDmacro - if WORD_OUTS_OK - out dx,ax - else - out dx,al - inc dx - xchg ah,al - out dx,al - dec dx - xchg ah,al - endif - endm - ; - ; Macro to output a constant value to an indexed VGA - ; register. - ; - CONSTANT_TO_INDEXED_REGISTERmacro ADDRESS, INDEX, VALUE - movdx, ADDRESS - movax, (VALUE shl 8) + INDEX - OUT_WORD - endm - ; - Code segment - assume cs:Code, ds:Data - Start proc near - cld - mov ax, Data - mov ds, ax - ; - ; Set 640x350 16-color mode. - ; - mov ax,0010h ;AH=0 means select mode - ;AL=10h means select - ; mode 10h - int 10h ;BIOS video interrupt - ; - ; Set the palette up to provide bit-plane precedence. If - ; planes 0 & 1 overlap, the plane 0 color will be shown; - ; if planes 1 & 2 overlap, the plane 1 color will be - ; shown; and so on. - ; - mov ax,(10h shl 8) + 2 ;AH = 10h means - ; set palette - ; registers fn - ;AL = 2 means set - ; all palette - ; registers - push ds ;ES:DX points to - pop es ; the palette - mov dx,offset Colors ; settings - int 10h ;call the BIOS to - ; set the palette - ; - ; Draw the static backdrop in plane 3. All the moving images - ; will appear to be in front of this backdrop, since plane 3 - ; has the lowest precedence the way the palette is set up. - ; - CONSTANT_TO_INDEXED_REGISTER SC_INDEX, MAP_MASK, 08h - ;allow data to go to - ; plane 3 only - ; - ; Point ES to display memory for the rest of the program. - ; - mov ax,VGA_SEGMENT - mov es,ax - ; - sub di,di - mov bp,SCREEN_HEIGHT/16 ;fill in the screen - ; 16 lines at a time - BackdropBlockLoop: - call DrawGridCross ;draw a cross piece - call DrawGridVert ;draw the rest of a - ; 15-high block - dec bp - jnz BackdropBlockLoop - call DrawGridCross ;bottom line of grid - ; - ; Start animating! - ; - AnimationLoop: - 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 - ; object. - ; - ObjectLoop: - ; - ; See if it's time to move this object. - ; - dec [bx+Delay] ;count down delay - jnz DoNextObject ;still delaying-don't move - mov ax,[bx+BaseDelay] - mov [bx+Delay],ax ;reset delay for next time - ; - ; Select the plane that this object will be drawn in. - ; - mov dx,SC_INDEX - mov ah,[bx+PlaneSelect] - mov al,MAP_MASK - OUT_WORD - ; - ; Advance the X coordinate, reversing direction if either - ; of the X margins has been reached. - ; - mov cx,[bx+XCoord] ;current X location - cmp cx,[bx+XLeftLimit] ;at left limit? - ja CheckXRightLimit ;no - neg [bx+XInc] ;yes-reverse - CheckXRightLimit: - cmp cx,[bx+XRightLimit] ;at right limit? - jb SetNewX ;no - neg [bx+XInc] ;yes-reverse - SetNewX: - add cx,[bx+XInc] ;move the X coord - mov [bx+XCoord],cx ; & save it - ; - ; Advance the Y coordinate, reversing direction if either - ; of the Y margins has been reached. - ; - mov dx,[bx+YCoord] ;current Y location - cmp dx,[bx+YTopLimit] ;at top limit? - ja CheckYBottomLimit ;no - neg [bx+YInc] ;yes-reverse - CheckYBottomLimit: - cmp dx,[bx+YBottomLimit] ;at bottom limit? - jb SetNewY ;no - neg [bx+YInc] ;yes-reverse - SetNewY: - add dx,[bx+YInc] ;move the Y coord - mov [bx+YCoord],dx ; & save it - ; - ; Draw at the new location. Because of the plane select - ; above, only one plane will be affected. - ; - mov si,[bx+Image] ;point to the - ; object's image - ; info - call DrawObject - ; - ; Point to the next object in the list until we run out of - ; objects. - ; - DoNextObject: - add bx,size ObjectStructure - cmp bx,offset ObjectListEnd - jb ObjectLoop - ; - ; Delay as specified to slow things down. - ; - if SLOWDOWN - mov cx,SLOWDOWN - DelayLoop: - loop DelayLoop - endif - ; - ; If a key's been pressed, we're done, otherwise animate - ; again. - ; - CheckKey: - mov ah,1 - int 16h ;is a key waiting? - jz AnimationLoop ;no - sub ah,ah - int 16h ;yes-clear the key & done - ; - ; Back to text mode. - ; - mov ax,0003h ;AL=03h means select - ; mode 03h - int 10h - ; - ; Back to DOS. - ; - mov ah,4ch ;DOS terminate function - int 21h ;done - ; - Start endp - ; - ; Draws a single grid cross-element at the display memory - ; location pointed to by ES:DI. 1 horizontal line is drawn - ; across the screen. - ; - ; Input: ES:DI points to the address at which to draw - ; - ; Output: ES:DI points to the address following the - ; line drawn - ; - ; Registers altered: AX, CX, DI - ; - DrawGridCross proc near - mov ax,0ffffh ;draw a solid line - mov cx,SCREEN_WIDTH/2-1 - rep stosw ;draw all but the rightmost - ; edge - mov ax,0080h - stosw ;draw the right edge of the - ; grid - ret - DrawGridCross endp - ; - ; Draws the non-cross part of the grid at the display memory - ; location pointed to by ES:DI. 15 scan lines are filled. - ; - ; Input: ES:DI points to the address at which to draw - ; - ; Output: ES:DI points to the address following the - ; part of the grid drawn - ; - ; Registers altered: AX, CX, DX, DI - ; - DrawGridVert proc near - mov ax,0080h ;pattern for a vertical line - mov dx,15 ;draw 15 scan lines (all of - ; a grid block except the - ; solid cross line) - BackdropRowLoop: - mov cx,SCREEN_WIDTH/2 - rep stosw ;draw this scan line's bit - ; of all the vertical lines - ; on the screen - dec dx - jnz BackdropRowLoop - ret - DrawGridVert endp - ; - ; Draw the specified image at the specified location. - ; Images are drawn on byte boundaries horizontally, pixel - ; boundaries vertically. - ; The Map Mask register must already have been set to enable - ; access to the desired plane. - ; - ; Input: - ; CX - X coordinate of upper left corner - ; DX - Y coordinate of upper left corner - ; DS:SI - pointer to draw info for image - ; ES - display memory segment - ; - ; Output: none - ; - ; Registers altered: AX, CX, DX, SI, DI, BP - ; - DrawObject proc near - mov ax,SCREEN_WIDTH - mul dx ;calculate the start offset in - ; display memory of the row the - ; image will be drawn at - shr cx,1 - shr cx,1 - shr cx,1 ;divide the X coordinate in pixels - ; by 8 to get the X coordinate in - ; bytes - add ax,cx ;destination offset in display - ; memory for the image - mov di,ax ;point ES:DI to the address to - ; which the image will be copied - ; in display memory - lodsw - mov dx,ax ;# of lines in the image - lodsw ;# of bytes across the image - mov bp,SCREEN_WIDTH - subbp,ax ;# of bytes to add to the display - ; memory offset after copying a line - ; of the image to display memory in - ; order to point to the address - ; where the next line of the image - ; will go in display memory - DrawLoop: - mov cx,ax ;width of the image - rep movsb ;copy the next line of the image - ; into display memory - add di,bp ;point to the address at which the - ; next line will go in display - ; memory - dec dx ;count down the lines of the image - jnzDrawLoop - ret - DrawObjectendp - ; - Code ends - end Start +```nasm +; Program to demonstrate bit-plane animation. Performs +; flicker-free animation with image transparency and +; image precedence across four distinct planes, with +; 13 32x32 images kept in motion at once. +; +; +; Set to higher values to slow down on faster computers. +; 0 is fine for a PC. 500 is a reasonable setting for an AT. +; Slowing animation further allows a good look at +; transparency and the lack of flicker and color effects +; when images cross. +; +SLOWDOWN equ 10000 +; +; Plane selects for the four colors we're using. +; +RED equ 01h +GREEN equ 02h +BLUE equ 04h +WHITE equ 08h +; +VGA_SEGMENT equ 0a000h ;mode 10h display memory + ; segment +SC_INDEX equ 3c4h ;Sequence Controller Index + ; register +MAP_MASK equ 2 ;Map Mask register index in + ; Sequence Controller +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 + ; handle word outs to + ; indexed VGA regs + ; +stack segment para stack ‘STACK' + db 512 dup (?) +stack ends +; +; Complete info about one object that we're animating. +; +ObjectStructure struc +Delay dw ? ;used to delay for n passes + ; throught the loop to + ; control animation speed +BaseDelay dw ? ;reset value for Delay +Image dw ? ;pointer to drawing info + ; for object +XCoord dw ? ;object X location in pixels +XInc dw ? ;# of pixels to increment + ; location by in the X + ; direction on each move +XLeftLimit dw ? ;left limit of X motion +XRightLimit dw ? ;right limit of X motion +YCoord dw ? ;object Y location in pixels +YInc dw ? ;# of pixels to increment + ; location by in the Y + ; direction on each move +YTopLimit dw ? ;top limit of Y motion +YBottomLimit dw ? ;bottom limit of Y motion +PlaneSelect db ? ;mask to select plane to + ; which object is drawn + db ? ;to make an even # of words + ; long, for better 286 + ; performance (keeps the + ; following structure + ; word-aligned) +ObjectStructure ends +; +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 +; obscured by any other plane), while plane 0 has the +; highest precedence (displays in front of any other plane). +; +Colors db 000h ;background color=black + db 03ch ;plane 0 only=red + db 03ah ;plane 1 only=green + db 03ch ;planes 0&1=red (plane 0 priority) + db 039h ;plane 2 only=blue + db 03ch ;planes 0&2=red (plane 0 priority) + db 03ah ;planes 1&2=green (plane 1 priority) + db 03ch ;planes 0&1&2=red (plane 0 priority) + db 03fh ;plane 3 only=white + db 03ch ;planes 0&3=red (plane 0 priority) + db 03ah ;planes 1&3=green (plane 1 priority) + db 03ch ;planes 0&1&3=red (plane 0 priority) + db 039h ;planes 2&3=blue (plane 2 priority) + db 03ch ;planes 0&2&3=red (plane 0 priority) + db 03ah ;planes 1&2&3=green (plane 1 priority) + db 03ch ;planes 0&1&2&3=red (plane 0 priority) + db 000h ;border color=black +; +; Image of a hollow square. +; 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. +; +Square label byte + dw 48,6 ;height in pixels, width in bytes + rept 8 + db 0,0,0,0,0,0;top blank border + endm + .radix 2 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,00000000,00000000,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + db 0,11111111,11111111,11111111,11111111,0 + .radix 10 + rept 8 + db 0,0,0,0,0,0;bottom blank border + endm +; +; Image of a hollow diamond with a smaller diamond in the +; middle. +; 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. +; +Diamond label byte + dw 48,6 ;height in pixels, width in bytes + rept 8 + db 0,0,0,0,0,0;top blank border + endm + .radix 2 + db 0,00000000,00000001,10000000,00000000,0 + db 0,00000000,00000011,11000000,00000000,0 + db 0,00000000,00000111,11100000,00000000,0 + db 0,00000000,00001111,11110000,00000000,0 + db 0,00000000,00011111,11111000,00000000,0 + db 0,00000000,00111110,01111100,00000000,0 + db 0,00000000,01111100,00111110,00000000,0 + db 0,00000000,11111000,00011111,00000000,0 + db 0,00000001,11110000,00001111,10000000,0 + db 0,00000011,11100000,00000111,11000000,0 + db 0,00000111,11000000,00000011,11100000,0 + db 0,00001111,10000001,10000001,11110000,0 + db 0,00011111,00000011,11000000,11111000,0 + db 0,00111110,00000111,11100000,01111100,0 + db 0,01111100,00001111,11110000,00111110,0 + db 0,11111000,00011111,11111000,00011111,0 + db 0,11111000,00011111,11111000,00011111,0 + db 0,01111100,00001111,11110000,00111110,0 + db 0,00111110,00000111,11100000,01111100,0 + db 0,00011111,00000011,11000000,11111000,0 + db 0,00001111,10000001,10000001,11110000,0 + db 0,00000111,11000000,00000011,11100000,0 + db 0,00000011,11100000,00000111,11000000,0 + db 0,00000001,11110000,00001111,10000000,0 + db 0,00000000,11111000,00011111,00000000,0 + db 0,00000000,01111100,00111110,00000000,0 + db 0,00000000,00111110,01111100,00000000,0 + db 0,00000000,00011111,11111000,00000000,0 + db 0,00000000,00001111,11110000,00000000,0 + db 0,00000000,00000111,11100000,00000000,0 + db 0,00000000,00000011,11000000,00000000,0 + db 0,00000000,00000001,10000000,00000000,0 + .radix 10 + rept 8 + db 0,0,0,0,0,0; bottom blank border + endm +; +; List of objects to animate. +; +even;word-align for better 286 performance +; +ObjectListlabelObjectStructure + ObjectStructure <1,21,Diamond,88,8,80,512,16,0,0,350,RED> + ObjectStructure <1,15,Square,296,8,112,480,144,0,0,350,RED> + ObjectStructure <1,23,Diamond,88,8,80,512,256,0,0,350,RED> + ObjectStructure <1,13,Square,120,0,0,640,144,4,0,280,BLUE> + ObjectStructure <1,11,Diamond,208,0,0,640,144,4,0,280,BLUE> + ObjectStructure <1,8,Square,296,0,0,640,144,4,0,288,BLUE> + ObjectStructure <1,9,Diamond,384,0,0,640,144,4,0,288,BLUE> + ObjectStructure <1,14,Square,472,0,0,640,144,4,0,280,BLUE> + ObjectStructure <1,8,Diamond,200,8,0,576,48,6,0,280,GREEN> + ObjectStructure <1,8,Square,248,8,0,576,96,6,0,280,GREEN> + ObjectStructure <1,8,Diamond,296,8,0,576,144,6,0,280,GREEN> + ObjectStructure <1,8,Square,344,8,0,576,192,6,0,280,GREEN> + ObjectStructure <1,8,Diamond,392,8,0,576,240,6,0,280,GREEN> +ObjectListEndlabelObjectStructure +; +Dataends +; +; Macro to output a word value to a port. +; +OUT_WORDmacro +if WORD_OUTS_OK + out dx,ax +else + out dx,al + inc dx + xchg ah,al + out dx,al + dec dx + xchg ah,al +endif + endm +; +; Macro to output a constant value to an indexed VGA +; register. +; +CONSTANT_TO_INDEXED_REGISTERmacro ADDRESS, INDEX, VALUE + movdx, ADDRESS + movax, (VALUE shl 8) + INDEX + OUT_WORD + endm +; +Code segment + assume cs:Code, ds:Data +Start proc near + cld + mov ax, Data + mov ds, ax +; +; Set 640x350 16-color mode. +; + mov ax,0010h ;AH=0 means select mode + ;AL=10h means select + ; mode 10h + int 10h ;BIOS video interrupt +; +; Set the palette up to provide bit-plane precedence. If +; planes 0 & 1 overlap, the plane 0 color will be shown; +; if planes 1 & 2 overlap, the plane 1 color will be +; shown; and so on. +; + mov ax,(10h shl 8) + 2 ;AH = 10h means + ; set palette + ; registers fn + ;AL = 2 means set + ; all palette + ; registers + push ds ;ES:DX points to + pop es ; the palette + mov dx,offset Colors ; settings + int 10h ;call the BIOS to + ; set the palette +; +; Draw the static backdrop in plane 3. All the moving images +; will appear to be in front of this backdrop, since plane 3 +; has the lowest precedence the way the palette is set up. +; + CONSTANT_TO_INDEXED_REGISTER SC_INDEX, MAP_MASK, 08h + ;allow data to go to + ; plane 3 only +; +; Point ES to display memory for the rest of the program. +; + mov ax,VGA_SEGMENT + mov es,ax +; + sub di,di + mov bp,SCREEN_HEIGHT/16 ;fill in the screen + ; 16 lines at a time +BackdropBlockLoop: + call DrawGridCross ;draw a cross piece + call DrawGridVert ;draw the rest of a + ; 15-high block + dec bp + jnz BackdropBlockLoop + call DrawGridCross ;bottom line of grid +; +; Start animating! +; +AnimationLoop: + 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 +; object. +; +ObjectLoop: +; +; See if it's time to move this object. +; + dec [bx+Delay] ;count down delay + jnz DoNextObject ;still delaying-don't move + mov ax,[bx+BaseDelay] + mov [bx+Delay],ax ;reset delay for next time +; +; Select the plane that this object will be drawn in. +; + mov dx,SC_INDEX + mov ah,[bx+PlaneSelect] + mov al,MAP_MASK + OUT_WORD +; +; Advance the X coordinate, reversing direction if either +; of the X margins has been reached. +; + mov cx,[bx+XCoord] ;current X location + cmp cx,[bx+XLeftLimit] ;at left limit? + ja CheckXRightLimit ;no + neg [bx+XInc] ;yes-reverse +CheckXRightLimit: + cmp cx,[bx+XRightLimit] ;at right limit? + jb SetNewX ;no + neg [bx+XInc] ;yes-reverse +SetNewX: + add cx,[bx+XInc] ;move the X coord + mov [bx+XCoord],cx ; & save it +; +; Advance the Y coordinate, reversing direction if either +; of the Y margins has been reached. +; + mov dx,[bx+YCoord] ;current Y location + cmp dx,[bx+YTopLimit] ;at top limit? + ja CheckYBottomLimit ;no + neg [bx+YInc] ;yes-reverse +CheckYBottomLimit: + cmp dx,[bx+YBottomLimit] ;at bottom limit? + jb SetNewY ;no + neg [bx+YInc] ;yes-reverse +SetNewY: + add dx,[bx+YInc] ;move the Y coord + mov [bx+YCoord],dx ; & save it +; +; Draw at the new location. Because of the plane select +; above, only one plane will be affected. +; + mov si,[bx+Image] ;point to the + ; object's image + ; info + call DrawObject +; +; Point to the next object in the list until we run out of +; objects. +; +DoNextObject: + add bx,size ObjectStructure + cmp bx,offset ObjectListEnd + jb ObjectLoop +; +; Delay as specified to slow things down. +; +if SLOWDOWN + mov cx,SLOWDOWN +DelayLoop: + loop DelayLoop +endif +; +; If a key's been pressed, we're done, otherwise animate +; again. +; +CheckKey: + mov ah,1 + int 16h ;is a key waiting? + jz AnimationLoop ;no + sub ah,ah + int 16h ;yes-clear the key & done +; +; Back to text mode. +; + mov ax,0003h ;AL=03h means select + ; mode 03h + int 10h +; +; Back to DOS. +; + mov ah,4ch ;DOS terminate function + int 21h ;done +; +Start endp +; +; Draws a single grid cross-element at the display memory +; location pointed to by ES:DI. 1 horizontal line is drawn +; across the screen. +; +; Input: ES:DI points to the address at which to draw +; +; Output: ES:DI points to the address following the +; line drawn +; +; Registers altered: AX, CX, DI +; +DrawGridCross proc near + mov ax,0ffffh ;draw a solid line + mov cx,SCREEN_WIDTH/2-1 + rep stosw ;draw all but the rightmost + ; edge + mov ax,0080h + stosw ;draw the right edge of the + ; grid + ret +DrawGridCross endp +; +; Draws the non-cross part of the grid at the display memory +; location pointed to by ES:DI. 15 scan lines are filled. +; +; Input: ES:DI points to the address at which to draw +; +; Output: ES:DI points to the address following the +; part of the grid drawn +; +; Registers altered: AX, CX, DX, DI +; +DrawGridVert proc near + mov ax,0080h ;pattern for a vertical line + mov dx,15 ;draw 15 scan lines (all of + ; a grid block except the + ; solid cross line) +BackdropRowLoop: + mov cx,SCREEN_WIDTH/2 + rep stosw ;draw this scan line's bit + ; of all the vertical lines + ; on the screen + dec dx + jnz BackdropRowLoop + ret + DrawGridVert endp +; +; Draw the specified image at the specified location. +; Images are drawn on byte boundaries horizontally, pixel +; boundaries vertically. +; The Map Mask register must already have been set to enable +; access to the desired plane. +; +; Input: +; CX - X coordinate of upper left corner +; DX - Y coordinate of upper left corner +; DS:SI - pointer to draw info for image +; ES - display memory segment +; +; Output: none +; +; Registers altered: AX, CX, DX, SI, DI, BP +; +DrawObject proc near + mov ax,SCREEN_WIDTH + mul dx ;calculate the start offset in + ; display memory of the row the + ; image will be drawn at + shr cx,1 + shr cx,1 + shr cx,1 ;divide the X coordinate in pixels + ; by 8 to get the X coordinate in + ; bytes + add ax,cx ;destination offset in display + ; memory for the image + mov di,ax ;point ES:DI to the address to + ; which the image will be copied + ; in display memory + lodsw + mov dx,ax ;# of lines in the image + lodsw ;# of bytes across the image + mov bp,SCREEN_WIDTH + subbp,ax ;# of bytes to add to the display + ; memory offset after copying a line + ; of the image to display memory in + ; order to point to the address + ; where the next line of the image + ; will go in display memory +DrawLoop: + mov cx,ax ;width of the image + rep movsb ;copy the next line of the image + ; into display memory + add di,bp ;point to the address at which the + ; next line will go in display + ; memory + dec dx ;count down the lines of the image + jnzDrawLoop + ret +DrawObjectendp +; +Code ends + end Start +``` diff --git a/44-02.md b/44-02.md index afbbfd2..e52b0ac 100644 --- a/44-02.md +++ b/44-02.md @@ -12,290 +12,292 @@ pages: 820-825 **LISTING 44.1 L44-1.C** - /* Split screen VGA animation program. Performs page flipping in the - top portion of the screen while displaying non-page flipped - information in the split screen at the bottom of the screen. - Compiled with Borland C++ in C compilation mode. */ +```c +/* Split screen VGA animation program. Performs page flipping in the +top portion of the screen while displaying non-page flipped +information in the split screen at the bottom of the screen. +Compiled with Borland C++ in C compilation mode. */ - #include - #include - #include - #include +#include +#include +#include +#include - #define SCREEN-SEG 0xA000 - #define SCREEN-PIXWIDTH 640 /* in pixels */ - #define SCREEN-WIDTH 80 /* in bytes */ - #define SPLIT-START-LINE 339 - #define SPLIT-LINES 141 - #define NONSPLIT-LINES 339 - #define SPLIT-START-OFFSET 0 - #define PAGE0-START-OFFSET (SPLIT-LINES*SCREEN-WIDTH) - #define PAGE1-START-OFFSET ((SPLIT-LINES+NONSPLIT-LINES)*SCREEN-WIDTH) - #define CRTC-INDEX 0x3D4 /* CRT Controller Index register */ - #define CRTC-DATA 0x3D5 /* CRT Controller Data register */ - #define OVERFLOW 0x07 /* index of CRTC reg holding bit 8 of the - line the split screen starts after */ - #define MAX-SCAN 0x09 /* index of CRTC reg holding bit 9 of the - line the split screen starts after */ - #define LINE-COMPARE 0x18 /* index of CRTC reg holding lower 8 bits - of line split screen starts after */ - #define NUM-BUMPERS (sizeof(Bumpers)/sizeof(bumper)) - #define BOUNCER-COLOR 15 - #define BACK-COLOR 1 /* playfield background color */ +#define SCREEN-SEG 0xA000 +#define SCREEN-PIXWIDTH 640 /* in pixels */ +#define SCREEN-WIDTH 80 /* in bytes */ +#define SPLIT-START-LINE 339 +#define SPLIT-LINES 141 +#define NONSPLIT-LINES 339 +#define SPLIT-START-OFFSET 0 +#define PAGE0-START-OFFSET (SPLIT-LINES*SCREEN-WIDTH) +#define PAGE1-START-OFFSET ((SPLIT-LINES+NONSPLIT-LINES)*SCREEN-WIDTH) +#define CRTC-INDEX 0x3D4 /* CRT Controller Index register */ +#define CRTC-DATA 0x3D5 /* CRT Controller Data register */ +#define OVERFLOW 0x07 /* index of CRTC reg holding bit 8 of the + line the split screen starts after */ +#define MAX-SCAN 0x09 /* index of CRTC reg holding bit 9 of the + line the split screen starts after */ +#define LINE-COMPARE 0x18 /* index of CRTC reg holding lower 8 bits + of line split screen starts after */ +#define NUM-BUMPERS (sizeof(Bumpers)/sizeof(bumper)) +#define BOUNCER-COLOR 15 +#define BACK-COLOR 1 /* playfield background color */ - typedef struct { /* one solid bumper to be bounced off of */ - int LeftX,TopY,RightX,BottomY; - int Color; - } bumper; +typedef struct { /* one solid bumper to be bounced off of */ + int LeftX,TopY,RightX,BottomY; + int Color; +} bumper; - typedef struct { /* one bit pattern to be used for drawing */ - int WidthInBytes; - int Height; - unsigned char *BitPattern; - } image; +typedef struct { /* one bit pattern to be used for drawing */ + int WidthInBytes; + int Height; + unsigned char *BitPattern; +} image; - typedef struct { /* one bouncing object to move around the screen */ - int LeftX,TopY; /* location */ - int Width,Height; /* size in pixels */ - int DirX,DirY; /* motion vectors */ - int CurrentX[2],CurrentY[2]; /* current location in each page */ - int Color; /* color in which to be drawn */ - image *Rotation0; /* rotations for handling the 8 possible */ - image *Rotation1; /* intrabyte start address at which the */ - image *Rotation2; /* left edge can be */ - image *Rotation3; - image *Rotation4; - image *Rotation5; - image *Rotation6; - image *Rotation7; - } bouncer; +typedef struct { /* one bouncing object to move around the screen */ + int LeftX,TopY; /* location */ + int Width,Height; /* size in pixels */ + int DirX,DirY; /* motion vectors */ + int CurrentX[2],CurrentY[2]; /* current location in each page */ + int Color; /* color in which to be drawn */ + image *Rotation0; /* rotations for handling the 8 possible */ + image *Rotation1; /* intrabyte start address at which the */ + image *Rotation2; /* left edge can be */ + image *Rotation3; + image *Rotation4; + image *Rotation5; + image *Rotation6; + image *Rotation7; +} bouncer; - void main(void); - void DrawBumperList(bumper *, int, unsigned int); - void DrawSplitScreen(void); - void EnableSplitScreen(void); - void MoveBouncer(bouncer *, bumper *, int); - extern void DrawRect(int,int,int,int,int,unsigned int,unsigned int); - extern void ShowPage(unsigned int); - extern void DrawImage(int,int,image **,int,unsigned int,unsigned int); - extern void ShowBounceCount(void); - extern void TextUp(char *,int,int,unsigned int,unsigned int); - extern void SetBIOS8x8Font(void); +void main(void); +void DrawBumperList(bumper *, int, unsigned int); +void DrawSplitScreen(void); +void EnableSplitScreen(void); +void MoveBouncer(bouncer *, bumper *, int); +extern void DrawRect(int,int,int,int,int,unsigned int,unsigned int); +extern void ShowPage(unsigned int); +extern void DrawImage(int,int,image **,int,unsigned int,unsigned int); +extern void ShowBounceCount(void); +extern void TextUp(char *,int,int,unsigned int,unsigned int); +extern void SetBIOS8x8Font(void); - /* All bumpers in the playfield */ - bumper Bumpers[] = { - {0,0,19,339,2}, {0,0,639,19,2}, {620,0,639,339,2}, - {0,320,639,339,2}, {60,48,79,67,12}, {60,108,79,127,12}, - {60,168,79,187,12}, {60,228,79,247,12}, {120,68,131,131,13}, - {120,188,131,271,13}, {240,128,259,147,14}, {240,192,259,211,14}, - {208,160,227,179,14}, {272,160,291,179,14}, {228,272,231,319,11}, - {192,52,211,55,11}, {302,80,351,99,12}, {320,260,379,267,13}, - {380,120,387,267,13}, {420,60,579,63,11}, {428,110,571,113,11}, - {420,160,579,163,11}, {428,210,571,213,11}, {420,260,579,263,11} }; +/* All bumpers in the playfield */ +bumper Bumpers[] = { + {0,0,19,339,2}, {0,0,639,19,2}, {620,0,639,339,2}, + {0,320,639,339,2}, {60,48,79,67,12}, {60,108,79,127,12}, + {60,168,79,187,12}, {60,228,79,247,12}, {120,68,131,131,13}, + {120,188,131,271,13}, {240,128,259,147,14}, {240,192,259,211,14}, + {208,160,227,179,14}, {272,160,291,179,14}, {228,272,231,319,11}, + {192,52,211,55,11}, {302,80,351,99,12}, {320,260,379,267,13}, + {380,120,387,267,13}, {420,60,579,63,11}, {428,110,571,113,11}, + {420,160,579,163,11}, {428,210,571,213,11}, {420,260,579,263,11} }; - /* Image for bouncing object when left edge is aligned with bit 7 */ - unsigned char -BouncerRotation0[] = { - 0xFF,0x0F,0xF0, 0xFE,0x07,0xF0, 0xFC,0x03,0xF0, 0xFC,0x03,0xF0, - 0xFE,0x07,0xF0, 0xFF,0xFF,0xF0, 0xCF,0xFF,0x30, 0x87,0xFE,0x10, - 0x07,0x0E,0x00, 0x07,0x0E,0x00, 0x07,0x0E,0x00, 0x07,0x0E,0x00, - 0x87,0xFE,0x10, 0xCF,0xFF,0x30, 0xFF,0xFF,0xF0, 0xFE,0x07,0xF0, - 0xFC,0x03,0xF0, 0xFC,0x03,0xF0, 0xFE,0x07,0xF0, 0xFF,0x0F,0xF0}; - image BouncerRotation0 = {3, 20, -BouncerRotation0}; +/* Image for bouncing object when left edge is aligned with bit 7 */ +unsigned char -BouncerRotation0[] = { + 0xFF,0x0F,0xF0, 0xFE,0x07,0xF0, 0xFC,0x03,0xF0, 0xFC,0x03,0xF0, + 0xFE,0x07,0xF0, 0xFF,0xFF,0xF0, 0xCF,0xFF,0x30, 0x87,0xFE,0x10, + 0x07,0x0E,0x00, 0x07,0x0E,0x00, 0x07,0x0E,0x00, 0x07,0x0E,0x00, + 0x87,0xFE,0x10, 0xCF,0xFF,0x30, 0xFF,0xFF,0xF0, 0xFE,0x07,0xF0, + 0xFC,0x03,0xF0, 0xFC,0x03,0xF0, 0xFE,0x07,0xF0, 0xFF,0x0F,0xF0}; +image BouncerRotation0 = {3, 20, -BouncerRotation0}; - /* Image for bouncing object when left edge is aligned with bit 3 */ - unsigned char -BouncerRotation4[] = { - 0x0F,0xF0,0xFF, 0x0F,0xE0,0x7F, 0x0F,0xC0,0x3F, 0x0F,0xC0,0x3F, - 0x0F,0xE0,0x7F, 0x0F,0xFF,0xFF, 0x0C,0xFF,0xF3, 0x08,0x7F,0xE1, - 0x00,0x70,0xE0, 0x00,0x70,0xE0, 0x00,0x70,0xE0, 0x00,0x70,0xE0, - 0x08,0x7F,0xE1, 0x0C,0xFF,0xF3, 0x0F,0xFF,0xFF, 0x0F,0xE0,0x7F, - 0x0F,0xC0,0x3F, 0x0F,0xC0,0x3F, 0x0F,0xE0,0x7F, 0x0F,0xF0,0xFF}; - image BouncerRotation4 = {3, 20, -BouncerRotation4}; +/* Image for bouncing object when left edge is aligned with bit 3 */ +unsigned char -BouncerRotation4[] = { + 0x0F,0xF0,0xFF, 0x0F,0xE0,0x7F, 0x0F,0xC0,0x3F, 0x0F,0xC0,0x3F, + 0x0F,0xE0,0x7F, 0x0F,0xFF,0xFF, 0x0C,0xFF,0xF3, 0x08,0x7F,0xE1, + 0x00,0x70,0xE0, 0x00,0x70,0xE0, 0x00,0x70,0xE0, 0x00,0x70,0xE0, + 0x08,0x7F,0xE1, 0x0C,0xFF,0xF3, 0x0F,0xFF,0xFF, 0x0F,0xE0,0x7F, + 0x0F,0xC0,0x3F, 0x0F,0xC0,0x3F, 0x0F,0xE0,0x7F, 0x0F,0xF0,0xFF}; +image BouncerRotation4 = {3, 20, -BouncerRotation4}; - /* Initial settings for bouncing object. Only 2 rotations are needed - because the object moves 4 pixels horizontally at a time */ - bouncer Bouncer = {156,60,20,20,4,4,156,156,60,60,BOUNCER-COLOR, - &BouncerRotation0,NULL,NULL,NULL,&BouncerRotation4,NULL,NULL,NULL}; - unsigned int PageStartOffsets[2] = - {PAGE0-START-OFFSET,PAGE1-START-OFFSET}; - unsigned int BounceCount; +/* Initial settings for bouncing object. Only 2 rotations are needed + because the object moves 4 pixels horizontally at a time */ +bouncer Bouncer = {156,60,20,20,4,4,156,156,60,60,BOUNCER-COLOR, + &BouncerRotation0,NULL,NULL,NULL,&BouncerRotation4,NULL,NULL,NULL}; +unsigned int PageStartOffsets[2] = + {PAGE0-START-OFFSET,PAGE1-START-OFFSET}; +unsigned int BounceCount; - void main() { - int DisplayedPage, NonDisplayedPage, Done, i; - union REGS regset; +void main() { + int DisplayedPage, NonDisplayedPage, Done, i; + union REGS regset; - regset.x.ax = 0x0012; /* set display to 640x480 16-color mode */ - int86(0x10, ®set, ®set); - SetBIOS8x8Font(); /* set the pointer to the BIOS 8x8 font */ - EnableSplitScreen(); /* turn on the split screen */ + regset.x.ax = 0x0012; /* set display to 640x480 16-color mode */ + int86(0x10, ®set, ®set); + SetBIOS8x8Font(); /* set the pointer to the BIOS 8x8 font */ + EnableSplitScreen(); /* turn on the split screen */ - /* Display page 0 above the split screen */ - ShowPage(PageStartOffsets[DisplayedPage = 0]); + /* Display page 0 above the split screen */ + ShowPage(PageStartOffsets[DisplayedPage = 0]); - /* Clear both pages to background and draw bumpers in each page */ - for (i=0; i<2; i++) { - DrawRect(0,0,SCREEN-PIXWIDTH-1,NONSPLIT-LINES-1,BACK-COLOR, - PageStartOffsets[i],SCREEN-SEG); - DrawBumperList(Bumpers,NUM-BUMPERS,PageStartOffsets[i]); - } + /* Clear both pages to background and draw bumpers in each page */ + for (i=0; i<2; i++) { + DrawRect(0,0,SCREEN-PIXWIDTH-1,NONSPLIT-LINES-1,BACK-COLOR, + PageStartOffsets[i],SCREEN-SEG); + DrawBumperList(Bumpers,NUM-BUMPERS,PageStartOffsets[i]); + } - DrawSplitScreen(); /* draw the static split screen info */ - BounceCount = 0; - ShowBounceCount(); /* put up the initial zero count */ + DrawSplitScreen(); /* draw the static split screen info */ + BounceCount = 0; + ShowBounceCount(); /* put up the initial zero count */ - /* Draw the bouncing object at its initial location */ - DrawImage(Bouncer.LeftX,Bouncer.TopY,&Bouncer.Rotation0, - Bouncer.Color,PageStartOffsets[DisplayedPage],SCREEN-SEG); + /* Draw the bouncing object at its initial location */ + DrawImage(Bouncer.LeftX,Bouncer.TopY,&Bouncer.Rotation0, + Bouncer.Color,PageStartOffsets[DisplayedPage],SCREEN-SEG); - /* Move the object, draw it in the nondisplayed page, and flip the - page until Esc is pressed */ - Done = 0; - do { - NonDisplayedPage = DisplayedPage ^ 1; - /* Erase at current location in the nondisplayed page */ - DrawRect(Bouncer.CurrentX[NonDisplayedPage], - Bouncer.CurrentY[NonDisplayedPage], - Bouncer.CurrentX[NonDisplayedPage]+Bouncer.Width-1, - Bouncer.CurrentY[NonDisplayedPage]+Bouncer.Height-1, - BACK-COLOR,PageStartOffsets[NonDisplayedPage],SCREEN-SEG); - /* Move the bouncer */ - MoveBouncer(&Bouncer, Bumpers, NUM-BUMPERS); - /* Draw at the new location in the nondisplayed page */ - DrawImage(Bouncer.LeftX,Bouncer.TopY,&Bouncer.Rotation0, - Bouncer.Color,PageStartOffsets[NonDisplayedPage], - SCREEN-SEG); - /* Remember where the bouncer is in the nondisplayed page */ - Bouncer.CurrentX[NonDisplayedPage] = Bouncer.LeftX; - Bouncer.CurrentY[NonDisplayedPage] = Bouncer.TopY; - /* Flip to the page we just drew into */ - ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]); - /* Respond to any keystroke */ - if (kbhit()) { - switch (getch()) { - case 0x1B: /* Esc to end */ - Done = 1; break; - case 0: /* branch on the extended code */ - switch (getch()) { - case 0x48: /* nudge up */ - Bouncer.DirY = -abs(Bouncer.DirY); break; - case 0x4B: /* nudge left */ - Bouncer.DirX = -abs(Bouncer.DirX); break; - case 0x4D: /* nudge right */ - Bouncer.DirX = abs(Bouncer.DirX); break; - case 0x50: /* nudge down */ - Bouncer.DirY = abs(Bouncer.DirY); break; - } - break; - default: - break; - } - } - } while (!Done); + /* Move the object, draw it in the nondisplayed page, and flip the + page until Esc is pressed */ + Done = 0; + do { + NonDisplayedPage = DisplayedPage ^ 1; + /* Erase at current location in the nondisplayed page */ + DrawRect(Bouncer.CurrentX[NonDisplayedPage], + Bouncer.CurrentY[NonDisplayedPage], + Bouncer.CurrentX[NonDisplayedPage]+Bouncer.Width-1, + Bouncer.CurrentY[NonDisplayedPage]+Bouncer.Height-1, + BACK-COLOR,PageStartOffsets[NonDisplayedPage],SCREEN-SEG); + /* Move the bouncer */ + MoveBouncer(&Bouncer, Bumpers, NUM-BUMPERS); + /* Draw at the new location in the nondisplayed page */ + DrawImage(Bouncer.LeftX,Bouncer.TopY,&Bouncer.Rotation0, + Bouncer.Color,PageStartOffsets[NonDisplayedPage], + SCREEN-SEG); + /* Remember where the bouncer is in the nondisplayed page */ + Bouncer.CurrentX[NonDisplayedPage] = Bouncer.LeftX; + Bouncer.CurrentY[NonDisplayedPage] = Bouncer.TopY; + /* Flip to the page we just drew into */ + ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]); + /* Respond to any keystroke */ + if (kbhit()) { + switch (getch()) { + case 0x1B: /* Esc to end */ + Done = 1; break; + case 0: /* branch on the extended code */ + switch (getch()) { + case 0x48: /* nudge up */ + Bouncer.DirY = -abs(Bouncer.DirY); break; + case 0x4B: /* nudge left */ + Bouncer.DirX = -abs(Bouncer.DirX); break; + case 0x4D: /* nudge right */ + Bouncer.DirX = abs(Bouncer.DirX); break; + case 0x50: /* nudge down */ + Bouncer.DirY = abs(Bouncer.DirY); break; + } + break; + default: + break; + } + } + } while (!Done); - /* Restore text mode and done */ - regset.x.ax = 0x0003; - int86(0x10, ®set, ®set); - } + /* Restore text mode and done */ + regset.x.ax = 0x0003; + int86(0x10, ®set, ®set); +} - /* Draws the specified list of bumpers into the specified page */ - void DrawBumperList(bumper * Bumpers, int NumBumpers, - unsigned int PageStartOffset) - { - int i; +/* Draws the specified list of bumpers into the specified page */ +void DrawBumperList(bumper * Bumpers, int NumBumpers, + unsigned int PageStartOffset) +{ + int i; - for (i=0; iLeftX,Bumpers->TopY,Bumpers->RightX, - Bumpers->BottomY,Bumpers->Color,PageStartOffset, - SCREEN-SEG); - } - } + for (i=0; iLeftX,Bumpers->TopY,Bumpers->RightX, + Bumpers->BottomY,Bumpers->Color,PageStartOffset, + SCREEN-SEG); + } +} - /* Displays the current bounce count */ - void ShowBounceCount() { - char CountASCII[7]; +/* Displays the current bounce count */ +void ShowBounceCount() { + char CountASCII[7]; - itoa(BounceCount,CountASCII,10); /* convert the count to ASCII*/ - TextUp(CountASCII,344,64,SPLIT-START-OFFSET,SCREEN-SEG); - } + itoa(BounceCount,CountASCII,10); /* convert the count to ASCII*/ + TextUp(CountASCII,344,64,SPLIT-START-OFFSET,SCREEN-SEG); +} - /* Frames the split screen and fills it with various text */ - void DrawSplitScreen() { - DrawRect(0,0,SCREEN-PIXWIDTH-1,SPLIT-LINES-1,0,SPLIT-START-OFFSET, - SCREEN-SEG); - DrawRect(0,1,SCREEN-PIXWIDTH-1,4,15,SPLIT-START-OFFSET, - SCREEN-SEG); - DrawRect(0,SPLIT-LINES-4,SCREEN-PIXWIDTH-1,SPLIT-LINES-1,15, - SPLIT-START-OFFSET,SCREEN-SEG); - 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, - 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); - } +/* Frames the split screen and fills it with various text */ +void DrawSplitScreen() { + DrawRect(0,0,SCREEN-PIXWIDTH-1,SPLIT-LINES-1,0,SPLIT-START-OFFSET, + SCREEN-SEG); + DrawRect(0,1,SCREEN-PIXWIDTH-1,4,15,SPLIT-START-OFFSET, + SCREEN-SEG); + DrawRect(0,SPLIT-LINES-4,SCREEN-PIXWIDTH-1,SPLIT-LINES-1,15, + SPLIT-START-OFFSET,SCREEN-SEG); + 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, + 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 - split screen starts *after* the line specified by the LINE-COMPARE - register) (bit 8 of the split screen start line is stored in the - Overflow register, and bit 9 is in the Maximum Scan Line reg) */ - void EnableSplitScreen() { - outp(CRTC-INDEX, LINE-COMPARE); - outp(CRTC-DATA, (SPLIT-START-LINE - 1) & 0xFF); - outp(CRTC-INDEX, OVERFLOW); - outp(CRTC-DATA, (((((SPLIT-START-LINE - 1) & 0x100) >> 8) << 4) | - (inp(CRTC-DATA) & ~0x10))); - outp(CRTC-INDEX, MAX-SCAN); - outp(CRTC-DATA, (((((SPLIT-START-LINE - 1) & 0x200) >> 9) << 6) | - (inp(CRTC-DATA) & ~0x40))); - } +/* Turn on the split screen at the desired line (minus 1 because the + split screen starts *after* the line specified by the LINE-COMPARE + register) (bit 8 of the split screen start line is stored in the + Overflow register, and bit 9 is in the Maximum Scan Line reg) */ +void EnableSplitScreen() { + outp(CRTC-INDEX, LINE-COMPARE); + outp(CRTC-DATA, (SPLIT-START-LINE - 1) & 0xFF); + outp(CRTC-INDEX, OVERFLOW); + outp(CRTC-DATA, (((((SPLIT-START-LINE - 1) & 0x100) >> 8) << 4) | + (inp(CRTC-DATA) & ~0x10))); + outp(CRTC-INDEX, MAX-SCAN); + outp(CRTC-DATA, (((((SPLIT-START-LINE - 1) & 0x200) >> 9) << 6) | + (inp(CRTC-DATA) & ~0x40))); +} - /* Moves the bouncer, bouncing if bumpers are hit */ - void MoveBouncer(bouncer *Bouncer, bumper *BumperPtr, int NumBumpers) { - int NewLeftX, NewTopY, NewRightX, NewBottomY, i; +/* Moves the bouncer, bouncing if bumpers are hit */ +void MoveBouncer(bouncer *Bouncer, bumper *BumperPtr, int NumBumpers) { + int NewLeftX, NewTopY, NewRightX, NewBottomY, i; - /* Move to new location, bouncing if necessary */ - NewLeftX = Bouncer->LeftX + Bouncer->DirX; /* new coords */ - NewTopY = Bouncer->TopY + Bouncer->DirY; - NewRightX = NewLeftX + Bouncer->Width - 1; - NewBottomY = NewTopY + Bouncer->Height - 1; - /* Compare the new location to all bumpers, checking for bounce */ - for (i=0; iRightX) && - (NewRightX >= BumperPtr->LeftX) && - (NewTopY <= BumperPtr->BottomY) && - (NewBottomY >= BumperPtr->TopY) ) { - /* The bouncer has tried to move into this bumper; figure - out which edge(s) it crossed, and bounce accordingly */ - if (((Bouncer->LeftX > BumperPtr->RightX) && - (NewLeftX <= BumperPtr->RightX)) || - (((Bouncer->LeftX + Bouncer->Width - 1) < - BumperPtr->LeftX) && - (NewRightX >= BumperPtr->LeftX))) { - Bouncer->DirX = -Bouncer->DirX; /* bounce horizontally */ - NewLeftX = Bouncer->LeftX + Bouncer->DirX; - } - if (((Bouncer->TopY > BumperPtr->BottomY) && - (NewTopY <= BumperPtr->BottomY)) || - (((Bouncer->TopY + Bouncer->Height - 1) < - BumperPtr->TopY) && - (NewBottomY >= BumperPtr->TopY))) { - Bouncer->DirY = -Bouncer->DirY; /* bounce vertically */ - NewTopY = Bouncer->TopY + Bouncer->DirY; - } - /* Update the bounce count display; turn over at 10000 */ - if (++BounceCount >= 10000) { - TextUp("0 ",344,64,SPLIT-START-OFFSET,SCREEN-SEG); - BounceCount = 0; - } else { - ShowBounceCount(); - } - } - } - Bouncer->LeftX = NewLeftX; /* set the final new coordinates */ - Bouncer->TopY = NewTopY; - } + /* Move to new location, bouncing if necessary */ + NewLeftX = Bouncer->LeftX + Bouncer->DirX; /* new coords */ + NewTopY = Bouncer->TopY + Bouncer->DirY; + NewRightX = NewLeftX + Bouncer->Width - 1; + NewBottomY = NewTopY + Bouncer->Height - 1; + /* Compare the new location to all bumpers, checking for bounce */ + for (i=0; iRightX) && + (NewRightX >= BumperPtr->LeftX) && + (NewTopY <= BumperPtr->BottomY) && + (NewBottomY >= BumperPtr->TopY) ) { + /* The bouncer has tried to move into this bumper; figure + out which edge(s) it crossed, and bounce accordingly */ + if (((Bouncer->LeftX > BumperPtr->RightX) && + (NewLeftX <= BumperPtr->RightX)) || + (((Bouncer->LeftX + Bouncer->Width - 1) < + BumperPtr->LeftX) && + (NewRightX >= BumperPtr->LeftX))) { + Bouncer->DirX = -Bouncer->DirX; /* bounce horizontally */ + NewLeftX = Bouncer->LeftX + Bouncer->DirX; + } + if (((Bouncer->TopY > BumperPtr->BottomY) && + (NewTopY <= BumperPtr->BottomY)) || + (((Bouncer->TopY + Bouncer->Height - 1) < + BumperPtr->TopY) && + (NewBottomY >= BumperPtr->TopY))) { + Bouncer->DirY = -Bouncer->DirY; /* bounce vertically */ + NewTopY = Bouncer->TopY + Bouncer->DirY; + } + /* Update the bounce count display; turn over at 10000 */ + if (++BounceCount >= 10000) { + TextUp("0 ",344,64,SPLIT-START-OFFSET,SCREEN-SEG); + BounceCount = 0; + } else { + ShowBounceCount(); + } + } + } + Bouncer->LeftX = NewLeftX; /* set the final new coordinates */ + Bouncer->TopY = NewTopY; +} +``` diff --git a/44-03.md b/44-03.md index 458bc59..7bf560d 100644 --- a/44-03.md +++ b/44-03.md @@ -12,325 +12,327 @@ pages: 825-830 **LISTING 44.2 L44-2.ASM** - ; Low-level animation routines. - ; Tested with TASM +```nasm +; Low-level animation routines. +; Tested with TASM - SCREEN-WIDTH equ 80 ;screen width in bytes - INPUT-STATUS-1 equ 03dah ;Input Status 1 register - CRTC-INDEX equ 03d4h ;CRT Controller Index reg - START-ADDRESS-HIGH equ 0ch ;bitmap start address high byte - START-ADDRESS-LOW equ 0dh ;bitmap start address low byte - GC-INDEX equ 03ceh ;Graphics Controller Index reg - SET-RESET equ 0 ;GC index of Set/Reset reg - G-MODE equ 5 ;GC index of Mode register +SCREEN-WIDTH equ 80 ;screen width in bytes +INPUT-STATUS-1 equ 03dah ;Input Status 1 register +CRTC-INDEX equ 03d4h ;CRT Controller Index reg +START-ADDRESS-HIGH equ 0ch ;bitmap start address high byte +START-ADDRESS-LOW equ 0dh ;bitmap start address low byte +GC-INDEX equ 03ceh ;Graphics Controller Index reg +SET-RESET equ 0 ;GC index of Set/Reset reg +G-MODE equ 5 ;GC index of Mode register - .model small - .data - BIOS8x8Ptr dd ? ;points to BIOS 8x8 font - ; Tables used to look up left and right clip masks. - LeftMask d 0ffh, 07fh, 03fh, 01fh, 00fh, 007h, 003h, 001h - RightMask d 080h, 0c0h, 0e0h, 0f0h, 0f8h, 0fch, 0feh, 0ffh + .model small + .data +BIOS8x8Ptr dd ? ;points to BIOS 8x8 font +; Tables used to look up left and right clip masks. +LeftMask d 0ffh, 07fh, 03fh, 01fh, 00fh, 007h, 003h, 001h +RightMask d 080h, 0c0h, 0e0h, 0f0h, 0f8h, 0fch, 0feh, 0ffh - .code - ; Draws the specified filled rectangle in the specified color. - ; Assumes the display is in mode 12h. Does not clip and assumes - ; rectangle coordinates are valid. - ; - ; C near-callable as: void DrawRect(int LeftX, int TopY, int RightX, - ; int BottomY, int Color, unsigned int ScrnOffset, - ; unsigned int ScrnSegment); + .code +; Draws the specified filled rectangle in the specified color. +; Assumes the display is in mode 12h. Does not clip and assumes +; rectangle coordinates are valid. +; +; C near-callable as: void DrawRect(int LeftX, int TopY, int RightX, +; int BottomY, int Color, unsigned int ScrnOffset, +; unsigned int ScrnSegment); - DrawRectParms struc - dw 2 dup (?) ;pushed BP and return address - LeftX dw ? ;X coordinate of left side of rectangle - TopY dw ? ;Y coordinate of top side of rectangle - RightX dw ? ;X coordinate of right side of rectangle - BottomY dw ? ;Y coordinate of bottom side of rectangle - Color dw ? ;color in which to draw rectangle (only the - ; lower 4 bits matter) - ScrnOffset dw ? ;offset of base of bitmap in which to draw - ScrnSegment dw ? ;segment of base of bitmap in which to draw - DrawRectParms ends +DrawRectParms struc + dw 2 dup (?) ;pushed BP and return address +LeftX dw ? ;X coordinate of left side of rectangle +TopY dw ? ;Y coordinate of top side of rectangle +RightX dw ? ;X coordinate of right side of rectangle +BottomY dw ? ;Y coordinate of bottom side of rectangle +Color dw ? ;color in which to draw rectangle (only the + ; lower 4 bits matter) +ScrnOffset dw ? ;offset of base of bitmap in which to draw +ScrnSegment dw ? ;segment of base of bitmap in which to draw +DrawRectParms ends - public -DrawRect - -DrawRect proc near - push bp ;preserve caller's stack frame - mov bp,sp ;point to local stack frame - push si ;preserve caller's register variables - push di + public -DrawRect +-DrawRect proc near + push bp ;preserve caller's stack frame + mov bp,sp ;point to local stack frame + push si ;preserve caller's register variables + push di - cld - mov dx,GC-INDEX - mov al,SET-RESET - mov ah,byte ptr Color[bp] - out dx,ax ;set the color in which to draw - mov ax,G-MODE + (0300h) - out dx,ax ;set to write mode 3 - les di,dword ptr ScrnOffset[bp] ;point to bitmap start - mov ax,SCREEN-WIDTH - mul TopY[bp] ;point to the start of the top scan - add di,ax ; line to fill - mov ax,LeftX[bp] - mov bx,ax - shr ax,1 ;/8 = byte offset from left of screen - shr ax,1 - shr ax,1 - add di,ax ;point to the upper-left corner of fill area - and bx,7 ;isolate intrapixel address - mov dl,LeftMask[bx] ;set the left-edge clip mask - mov bx,RightX[bp] - mov si,bx - and bx,7 ;isolate intrapixel address of right edge - mov dh,RightMask[bx] ;set the right-edge clip mask - mov bx,LeftX[bp] - and bx,NOT 7 ;intrapixel address of left edge - su si,bx - shr si,1 - shr si,1 - shr si,1 ;# of bytes across spanned by rectangle - 1 - jnz MasksSet ;if there's only one byte across, - and dl,dh ; combine the masks - MasksSet: - mov bx,BottomY[bp] - su bx,TopY[bp] ;# of scan lines to fill - 1 - FillLoop: - push di ;remember line start offset - mov al,dl ;left edge clip mask - xchg es:[di],al ;draw the left edge - inc di ;point to the next byte - mov cx,si ;# of bytes left to do - dec cx ;# of bytes left to do - 1 - js LineDone ;that's it if there's only 1 byte across - jz DrawRightEdge ;no middle bytes if only 2 bytes across - mov al,0ffh ;non-edge bytes are solid - rep stos ;draw the solid bytes across the middle - DrawRightEdge: - mov al,dh ;right-edge clip mask - xchg es:[di],al ;draw the right edge - LineDone: - pop di ;retrieve line start offset - add di,SCREEN-WIDTH ;point to the next line - dec bx ;count off scan lines - jns FillLoop + cld + mov dx,GC-INDEX + mov al,SET-RESET + mov ah,byte ptr Color[bp] + out dx,ax ;set the color in which to draw + mov ax,G-MODE + (0300h) + out dx,ax ;set to write mode 3 + les di,dword ptr ScrnOffset[bp] ;point to bitmap start + mov ax,SCREEN-WIDTH + mul TopY[bp] ;point to the start of the top scan + add di,ax ; line to fill + mov ax,LeftX[bp] + mov bx,ax + shr ax,1 ;/8 = byte offset from left of screen + shr ax,1 + shr ax,1 + add di,ax ;point to the upper-left corner of fill area + and bx,7 ;isolate intrapixel address + mov dl,LeftMask[bx] ;set the left-edge clip mask + mov bx,RightX[bp] + mov si,bx + and bx,7 ;isolate intrapixel address of right edge + mov dh,RightMask[bx] ;set the right-edge clip mask + mov bx,LeftX[bp] + and bx,NOT 7 ;intrapixel address of left edge + su si,bx + shr si,1 + shr si,1 + shr si,1 ;# of bytes across spanned by rectangle - 1 + jnz MasksSet ;if there's only one byte across, + and dl,dh ; combine the masks +MasksSet: + mov bx,BottomY[bp] + su bx,TopY[bp] ;# of scan lines to fill - 1 +FillLoop: + push di ;remember line start offset + mov al,dl ;left edge clip mask + xchg es:[di],al ;draw the left edge + inc di ;point to the next byte + mov cx,si ;# of bytes left to do + dec cx ;# of bytes left to do - 1 + js LineDone ;that's it if there's only 1 byte across + jz DrawRightEdge ;no middle bytes if only 2 bytes across + mov al,0ffh ;non-edge bytes are solid + rep stos ;draw the solid bytes across the middle +DrawRightEdge: + mov al,dh ;right-edge clip mask + xchg es:[di],al ;draw the right edge +LineDone: + pop di ;retrieve line start offset + add di,SCREEN-WIDTH ;point to the next line + dec bx ;count off scan lines + jns FillLoop - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - -DrawRect endp + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame + ret +-DrawRect endp - ; Shows the page at the specified offset in the bitmap. Page is - ; displayed when this routine returns. - ; - ; C near-callable as: void ShowPage(unsigned int StartOffset); +; Shows the page at the specified offset in the bitmap. Page is +; displayed when this routine returns. +; +; C near-callable as: void ShowPage(unsigned int StartOffset); - ShowPageParms struc - dw 2 dup (?) ;pushed BP and return address - StartOffset dw ? ;offset in bitmap of page to display - ShowPageParms ends +ShowPageParms struc + dw 2 dup (?) ;pushed BP and return address +StartOffset dw ? ;offset in bitmap of page to display +ShowPageParms ends - public -ShowPage - -ShowPage proc near - 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. - mov bl,START-ADDRESS-LOW ;preload for fastest - mov bh,byte ptr StartOffset[bp] ; flipping once display - mov cl,START-ADDRESS-HIGH ; enable is detected - mov ch,byte ptr StartOffset+1[bp] - mov dx,INPUT-STATUS-1 - WaitDE: - in al,dx - test al,01h - jnz WaitDE ;display enable is active low (0 = active) - ; Set the start offset in display memory of the page to display. - mov dx,CRTC-INDEX - mov ax,bx - out dx,ax ;start address low - mov ax,cx - out dx,ax ;start address high - ; Now wait for vertical sync, so the other page will be invisible when - ; we start drawing to it. - mov dx,INPUT-STATUS-1 - WaitVS: - in al,dx - test al,08h - jz WaitVS ;vertical sync is active high (1 = active) - pop bp ;restore caller's stack frame - ret - -ShowPage endp + public -ShowPage +-ShowPage proc near + 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. + mov bl,START-ADDRESS-LOW ;preload for fastest + mov bh,byte ptr StartOffset[bp] ; flipping once display + mov cl,START-ADDRESS-HIGH ; enable is detected + mov ch,byte ptr StartOffset+1[bp] + mov dx,INPUT-STATUS-1 +WaitDE: + in al,dx + test al,01h + jnz WaitDE ;display enable is active low (0 = active) +; Set the start offset in display memory of the page to display. + mov dx,CRTC-INDEX + mov ax,bx + out dx,ax ;start address low + mov ax,cx + out dx,ax ;start address high +; Now wait for vertical sync, so the other page will be invisible when +; we start drawing to it. + mov dx,INPUT-STATUS-1 +WaitVS: + in al,dx + test al,08h + jz WaitVS ;vertical sync is active high (1 = active) + pop bp ;restore caller's stack frame + ret +-ShowPage endp - ; Displays the specified image at the specified location in the - ; specified bitmap, in the desired color. - ; - ; C near-callable as: void DrawImage(int LeftX, int TopY, - ; image **RotationTable, int Color, unsigned int ScrnOffset, - ; unsigned int ScrnSegment); +; Displays the specified image at the specified location in the +; specified bitmap, in the desired color. +; +; C near-callable as: void DrawImage(int LeftX, int TopY, +; image **RotationTable, int Color, unsigned int ScrnOffset, +; unsigned int ScrnSegment); - DrawImageParms struc - dw 2 dup (?) ;pushed BP and return address - DILeftX dw ? ;X coordinate of left side of image - DITopY dw ? ;Y coordinate of top side of image - RotationTable dw ? ;pointer to table of pointers to image - ; rotations - DIColor dw ? ;color in which to draw image (only the - ; lower 4 bits matter) - DIScrnOffset dw ? ;offset of base of bitmap in which to draw - DIScrnSegment dw ? ;segment of base of bitmap in which to draw - DrawImageParms ends +DrawImageParms struc + dw 2 dup (?) ;pushed BP and return address +DILeftX dw ? ;X coordinate of left side of image +DITopY dw ? ;Y coordinate of top side of image +RotationTable dw ? ;pointer to table of pointers to image + ; rotations +DIColor dw ? ;color in which to draw image (only the + ; lower 4 bits matter) +DIScrnOffset dw ? ;offset of base of bitmap in which to draw +DIScrnSegment dw ? ;segment of base of bitmap in which to draw +DrawImageParms ends - image struc - WidthInBytes dw ? - Height dw ? - BitPattern dw ? - image ends +image struc +WidthInBytes dw ? +Height dw ? +BitPattern dw ? +image ends - public -DrawImage - -DrawImage proc near - push bp ;preserve caller's stack frame - mov bp,sp ;point to local stack frame - push si ;preserve caller's register variables - push di + public -DrawImage +-DrawImage proc near + push bp ;preserve caller's stack frame + mov bp,sp ;point to local stack frame + push si ;preserve caller's register variables + push di - cld - mov dx,GC-INDEX - mov al,SET-RESET - mov ah,byte ptr DIColor[bp] - out dx,ax ;set the color in which to draw - mov ax,G-MODE + (0300h) - out dx,ax ;set to write mode 3 - les di,dword ptr DIScrnOffset[bp] ;point to bitmap start - mov ax,SCREEN-WIDTH - mul DITopY[bp] ;point to the start of the top scan - add di,ax ; line on which to draw - mov ax,DILeftX[bp] - mov bx,ax - shr ax,1 ;/8 = byte offset from left of screen - shr ax,1 - shr ax,1 - add di,ax ;point to the upper-left corner of draw area - and bx,7 ;isolate intrapixel address - shl bx,1 ;*2 for word look-up - add bx,RotationTable[bp] ;point to the image structure for - mov bx,[bx] ; the intrabyte rotation - mov dx,[bx].WidthInBytes ;image width - mov si,[bx].BitPattern ;pointer to image pattern bytes - mov bx,[bx].Height ;image height - DrawImageLoop: - push di ;remember line start offset - mov cx,dx ;# of bytes across - DrawImageLineLoop: - lods ;get the next image byte - xchg es:[di],al ;draw the next image byte - inc di ;point to the following screen byte - loop DrawImageLineLoop - pop di ;retrieve line start offset - add di,SCREEN-WIDTH ;point to the next line - dec bx ;count off scan lines - jnz DrawImageLoop + cld + mov dx,GC-INDEX + mov al,SET-RESET + mov ah,byte ptr DIColor[bp] + out dx,ax ;set the color in which to draw + mov ax,G-MODE + (0300h) + out dx,ax ;set to write mode 3 + les di,dword ptr DIScrnOffset[bp] ;point to bitmap start + mov ax,SCREEN-WIDTH + mul DITopY[bp] ;point to the start of the top scan + add di,ax ; line on which to draw + mov ax,DILeftX[bp] + mov bx,ax + shr ax,1 ;/8 = byte offset from left of screen + shr ax,1 + shr ax,1 + add di,ax ;point to the upper-left corner of draw area + and bx,7 ;isolate intrapixel address + shl bx,1 ;*2 for word look-up + add bx,RotationTable[bp] ;point to the image structure for + mov bx,[bx] ; the intrabyte rotation + mov dx,[bx].WidthInBytes ;image width + mov si,[bx].BitPattern ;pointer to image pattern bytes + mov bx,[bx].Height ;image height +DrawImageLoop: + push di ;remember line start offset + mov cx,dx ;# of bytes across +DrawImageLineLoop: + lods ;get the next image byte + xchg es:[di],al ;draw the next image byte + inc di ;point to the following screen byte + loop DrawImageLineLoop + pop di ;retrieve line start offset + add di,SCREEN-WIDTH ;point to the next line + dec bx ;count off scan lines + jnz DrawImageLoop - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - -DrawImage endp + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame + ret +-DrawImage endp - ; Draws a 0-terminated text string at the specified location in the - ; specified bitmap in white, using the 8x8 BIOS font. Must be at an X - ; coordinate that's a multiple of 8. - ; - ; C near-callable as: void TextUp(char *Text, int LeftX, int TopY, - ; unsigned int ScrnOffset, unsigned int ScrnSegment); +; Draws a 0-terminated text string at the specified location in the +; specified bitmap in white, using the 8x8 BIOS font. Must be at an X +; coordinate that's a multiple of 8. +; +; C near-callable as: void TextUp(char *Text, int LeftX, int TopY, +; unsigned int ScrnOffset, unsigned int ScrnSegment); - TextUpParms struc - dw 2 dup (?) ;pushed BP and return address - Text dw ? ;pointer to text to draw - TULeftX dw ? ;X coordinate of left side of rectangle - ; (must be a multiple of 8) - TUTopY dw ? ;Y coordinate of top side of rectangle - TUScrnOffset dw ? ;offset of base of bitmap in which to draw - TUScrnSegment dw ? ;segment of base of bitmap in which to draw - TextUpParms ends +TextUpParms struc + dw 2 dup (?) ;pushed BP and return address +Text dw ? ;pointer to text to draw +TULeftX dw ? ;X coordinate of left side of rectangle + ; (must be a multiple of 8) +TUTopY dw ? ;Y coordinate of top side of rectangle +TUScrnOffset dw ? ;offset of base of bitmap in which to draw +TUScrnSegment dw ? ;segment of base of bitmap in which to draw +TextUpParms ends - public -TextUp - -TextUp proc near - push bp ;preserve caller's stack frame - mov bp,sp ;point to local stack frame - push si ;preserve caller's register variables - push di + public -TextUp +-TextUp proc near + push bp ;preserve caller's stack frame + mov bp,sp ;point to local stack frame + push si ;preserve caller's register variables + push di - cld - mov dx,GC-INDEX - mov ax,G-MODE + (0000h) - out dx,ax ;set to write mode 0 - les di,dword ptr TUScrnOffset[bp] ;point to bitmap start - mov ax,SCREEN-WIDTH - mul TUTopY[bp] ;point to the start of the top scan - add di,ax ; line the text starts on - mov ax,TULeftX[bp] - mov bx,ax - shr ax,1 ;/8 = byte offset from left of screen - shr ax,1 - shr ax,1 - add di,ax ;point to the upper-left corner of first char - mov si,Text[bp] ;point to text to draw - TextUpLoop: - lods ;get the next character to draw - and al,al - jz TextUpDone ;done if null byte - push si ;preserve text string pointer - push di ;preserve character's screen offset - push ds ;preserve default data segment - call CharUp ;draw this character - pop ds ;restore default data segment - pop di ;retrieve character's screen offset - pop si ;retrieve text string pointer - inc di ;point to next character's start location - jmp TextUpLoop + cld + mov dx,GC-INDEX + mov ax,G-MODE + (0000h) + out dx,ax ;set to write mode 0 + les di,dword ptr TUScrnOffset[bp] ;point to bitmap start + mov ax,SCREEN-WIDTH + mul TUTopY[bp] ;point to the start of the top scan + add di,ax ; line the text starts on + mov ax,TULeftX[bp] + mov bx,ax + shr ax,1 ;/8 = byte offset from left of screen + shr ax,1 + shr ax,1 + add di,ax ;point to the upper-left corner of first char + mov si,Text[bp] ;point to text to draw +TextUpLoop: + lods ;get the next character to draw + and al,al + jz TextUpDone ;done if null byte + push si ;preserve text string pointer + push di ;preserve character's screen offset + push ds ;preserve default data segment + call CharUp ;draw this character + pop ds ;restore default data segment + pop di ;retrieve character's screen offset + pop si ;retrieve text string pointer + inc di ;point to next character's start location + jmp TextUpLoop - TextUpDone: - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret +TextUpDone: + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame + ret - CharUp: ;draws the character in AL at ES:DI - lds si,[BIOS8x8Ptr] ;point to the 8x8 font start - mov bl,al - su bh,bh - shl bx,1 - shl bx,1 - shl bx,1 ;*8 to look up character offset in font - add si,bx ;point DS:Sito character data in font - mov cx,8 ;characters are 8 high - CharUpLoop: - movs ;copy the next character pattern byte - add di,SCREEN-WIDTH-1 ;point to the next dest byte - loop CharUpLoop - ret - -TextUp endp +CharUp: ;draws the character in AL at ES:DI + lds si,[BIOS8x8Ptr] ;point to the 8x8 font start + mov bl,al + su bh,bh + shl bx,1 + shl bx,1 + shl bx,1 ;*8 to look up character offset in font + add si,bx ;point DS:Sito character data in font + mov cx,8 ;characters are 8 high +CharUpLoop: + movs ;copy the next character pattern byte + add di,SCREEN-WIDTH-1 ;point to the next dest byte + loop CharUpLoop + ret +-TextUp endp - ; Sets the pointer to the BIOS 8x8 font. - ; - ; C near-callable as: extern void SetBIOS8x8Font(void); +; Sets the pointer to the BIOS 8x8 font. +; +; C near-callable as: extern void SetBIOS8x8Font(void); - public -SetBIOS8x8Font - -SetBIOS8x8Font proc near - push bp ;preserve caller's stack frame - push si ;preserve caller's register variables - push di ; and data segment (don't assume BIOS - push ds ; preserves anything) - mov ah,11h ;BIOS character generator function - mov al,30h ;BIOS information subfunction - mov bh,3 ;request 8x8 font pointer - int 10h ;invoke BIOS video services - mov word ptr [BIOS8x8Ptr],bp ;store the pointer - mov word ptr [BIOS8x8Ptr+2],es - pop ds - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - -SetBIOS8x8Font endp - end + public -SetBIOS8x8Font +-SetBIOS8x8Font proc near + push bp ;preserve caller's stack frame + push si ;preserve caller's register variables + push di ; and data segment (don't assume BIOS + push ds ; preserves anything) + mov ah,11h ;BIOS character generator function + mov al,30h ;BIOS information subfunction + mov bh,3 ;request 8x8 font pointer + int 10h ;invoke BIOS video services + mov word ptr [BIOS8x8Ptr],bp ;store the pointer + mov word ptr [BIOS8x8Ptr+2],es + pop ds + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame + ret +-SetBIOS8x8Font endp + end +``` diff --git a/44-04.md b/44-04.md index 7fa8047..b10d8b0 100644 --- a/44-04.md +++ b/44-04.md @@ -74,10 +74,12 @@ 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: - mov dx,0a000h - mov es,dx - mov al,es:[0] - mov byte ptr es:[0],0f0h +```nasm +mov dx,0a000h +mov es,dx +mov al,es:[0] +mov byte ptr es:[0],0f0h +``` will change the first 4 pixels on the screen (the left nibble of the byte at offset 0 in display memory) to blue, and will leave the next 4 @@ -88,10 +90,12 @@ display memory is not particularly efficient on some processors. In Listing 44.2, I instead use **XCHG**, which reads and then writes a memory location in a single operation, as in: - mov dx,0a000h - mov es,dx - mov al,0f0h - xchg es:[0],al +```nasm +mov dx,0a000h +mov es,dx +mov al,0f0h +xchg es:[0],al +``` Again, the actual value that's read is irrelevant. In general, the **XCHG** approach is more compact than two **MOV**s, and is faster on @@ -103,9 +107,11 @@ 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: - mov dx,0a000h - mov es,dx - mov byte ptr es:[di],0ffh +```nasm +mov dx,0a000h +mov es,dx +mov byte ptr es:[di],0ffh +``` > ![](images/i.jpg) > If you're familiar with VGA programming, you're no doubt aware that diff --git a/45-03.md b/45-03.md index 920ff8a..fe89b39 100644 --- a/45-03.md +++ b/45-03.md @@ -45,261 +45,263 @@ 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 - rectangles to minimize display memory accesses. Not even vaguely optimized! - Tested with Borland C++ in the small model. */ +```c +/* 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. */ - #include - #include - #include - #include - #include +#include +#include +#include +#include +#include - #define SCREEN_WIDTH 320 - #define SCREEN_HEIGHT 200 - #define SCREEN_SEGMENT 0xA000 +#define SCREEN_WIDTH 320 +#define SCREEN_HEIGHT 200 +#define SCREEN_SEGMENT 0xA000 - /* Describes a rectangle */ - typedef struct { - int Top; - int Left; - int Right; - int Bottom; - } Rectangle; +/* Describes a rectangle */ +typedef struct { + int Top; + int Left; + int Right; + int Bottom; +} Rectangle; - /* Describes an animated object */ - typedef struct { - int X; /* upper left corner in virtual bitmap */ - int Y; - int XDirection; /* direction and distance of movement */ - int YDirection; - } Entity; +/* Describes an animated object */ +typedef struct { + int X; /* upper left corner in virtual bitmap */ + int Y; + int XDirection; /* direction and distance of movement */ + int YDirection; +} Entity; - /* Storage used for dirty rectangles */ - #define MAX_DIRTY_RECTANGLES 100 - int NumDirtyRectangles; - Rectangle DirtyRectangles[MAX_DIRTY_RECTANGLES]; +/* Storage used for dirty rectangles */ +#define MAX_DIRTY_RECTANGLES 100 +int NumDirtyRectangles; +Rectangle DirtyRectangles[MAX_DIRTY_RECTANGLES]; - /* If set to 1, ignore dirty rectangle list and copy the whole screen. */ - int DrawWholeScreen = 0; +/* If set to 1, ignore dirty rectangle list and copy the whole screen. */ +int DrawWholeScreen = 0; - /* Pixels for image we'll animate */ - #define IMAGE_WIDTH 11 - #define IMAGE_HEIGHT 11 - char ImagePixels[] = { - 15,15,15, 9, 9, 9, 9, 9,15,15,15, - 15,15, 9, 9, 9, 9, 9, 9, 9,15,15, - 15, 9, 9,14,14,14,14,14, 9, 9,15, - 9, 9,14,14,14,14,14,14,14, 9, 9, - 9, 9,14,14,14,14,14,14,14, 9, 9, - 9, 9,14,14,14,14,14,14,14, 9, 9, - 9, 9,14,14,14,14,14,14,14, 9, 9, - 9, 9,14,14,14,14,14,14,14, 9, 9, - 15, 9, 9,14,14,14,14,14, 9, 9,15, - 15,15, 9, 9, 9, 9, 9, 9, 9,15,15, - 15,15,15, 9, 9, 9, 9, 9,15,15,15, - }; - /* animated entities */ - #define NUM_ENTITIES 10 - Entity Entities[NUM_ENTITIES]; +/* Pixels for image we'll animate */ +#define IMAGE_WIDTH 11 +#define IMAGE_HEIGHT 11 +char ImagePixels[] = { + 15,15,15, 9, 9, 9, 9, 9,15,15,15, + 15,15, 9, 9, 9, 9, 9, 9, 9,15,15, + 15, 9, 9,14,14,14,14,14, 9, 9,15, + 9, 9,14,14,14,14,14,14,14, 9, 9, + 9, 9,14,14,14,14,14,14,14, 9, 9, + 9, 9,14,14,14,14,14,14,14, 9, 9, + 9, 9,14,14,14,14,14,14,14, 9, 9, + 9, 9,14,14,14,14,14,14,14, 9, 9, + 15, 9, 9,14,14,14,14,14, 9, 9,15, + 15,15, 9, 9, 9, 9, 9, 9, 9,15,15, + 15,15,15, 9, 9, 9, 9, 9,15,15,15, +}; +/* animated entities */ +#define NUM_ENTITIES 10 +Entity Entities[NUM_ENTITIES]; - /* pointer to system buffer into which we'll draw */ - char far *SystemBufferPtr; +/* pointer to system buffer into which we'll draw */ +char far *SystemBufferPtr; - /* pointer to screen */ - char far *ScreenPtr; +/* pointer to screen */ +char far *ScreenPtr; - void EraseEntities(void); - void CopyDirtyRectanglesToScreen(void); - void DrawEntities(void); +void EraseEntities(void); +void CopyDirtyRectanglesToScreen(void); +void DrawEntities(void); - void main() - { - int i, XTemp, YTemp; - unsigned int TempCount; - char far *TempPtr; - union REGS regs; - /* 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"); - exit(1); - } - /* Clear the system buffer */ - TempPtr = SystemBufferPtr; - for (TempCount = ((unsigned)SCREEN_WIDTH*SCREEN_HEIGHT); TempCount--; ) { - *TempPtr++ = 0; - } - /* Point to the screen */ - ScreenPtr = MK_FP(SCREEN_SEGMENT, 0); +void main() +{ + int i, XTemp, YTemp; + unsigned int TempCount; + char far *TempPtr; + union REGS regs; + /* 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"); + exit(1); + } + /* Clear the system buffer */ + TempPtr = SystemBufferPtr; + for (TempCount = ((unsigned)SCREEN_WIDTH*SCREEN_HEIGHT); TempCount--; ) { + *TempPtr++ = 0; + } + /* Point to the screen */ + ScreenPtr = MK_FP(SCREEN_SEGMENT, 0); - /* 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); - Entities[i].Y = random(SCREEN_HEIGHT - IMAGE_HEIGHT); - Entities[i].XDirection = 1; - Entities[i].YDirection = -1; - } - /* Set 320x200 256-color graphics mode */ - regs.x.ax = 0x0013; - int86(0x10, ®s, ®s); + /* 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); + Entities[i].Y = random(SCREEN_HEIGHT - IMAGE_HEIGHT); + Entities[i].XDirection = 1; + Entities[i].YDirection = -1; + } + /* Set 320x200 256-color graphics mode */ + regs.x.ax = 0x0013; + int86(0x10, ®s, ®s); - /* Loop and draw until a key is pressed */ - do { - /* Draw the entities to the system buffer at their current locations, - updating the dirty rectangle list */ - DrawEntities(); + /* Loop and draw until a key is pressed */ + do { + /* Draw the entities to the system buffer at their current locations, + updating the dirty rectangle list */ + DrawEntities(); - /* Draw the dirty rectangles, or the whole system buffer if - appropriate */ - CopyDirtyRectanglesToScreen(); + /* Draw the dirty rectangles, or the whole system buffer if + appropriate */ + CopyDirtyRectanglesToScreen(); - /* Reset the dirty rectangle list to empty */ - NumDirtyRectangles = 0; + /* Reset the dirty rectangle list to empty */ + NumDirtyRectangles = 0; - /* Erase the entities in the system buffer at their old locations, - updating the dirty rectangle list */ - EraseEntities(); + /* Erase the entities in the system buffer at their old locations, + updating the dirty rectangle list */ + EraseEntities(); - /* Move the entities, bouncing off the edges of the screen */ - for (i = 0; i < NUM_ENTITIES; i++) { - XTemp = Entities[i].X + Entities[i].XDirection; - YTemp = Entities[i].Y + Entities[i].YDirection; - if ((XTemp < 0) || ((XTemp + IMAGE_WIDTH) > SCREEN_WIDTH)) { - Entities[i].XDirection = -Entities[i].XDirection; - XTemp = Entities[i].X + Entities[i].XDirection; - } - if ((YTemp < 0) || ((YTemp + IMAGE_HEIGHT) > SCREEN_HEIGHT)) { - Entities[i].YDirection = -Entities[i].YDirection; - YTemp = Entities[i].Y + Entities[i].YDirection; - } - Entities[i].X = XTemp; - Entities[i].Y = YTemp; - } + /* Move the entities, bouncing off the edges of the screen */ + for (i = 0; i < NUM_ENTITIES; i++) { + XTemp = Entities[i].X + Entities[i].XDirection; + YTemp = Entities[i].Y + Entities[i].YDirection; + if ((XTemp < 0) || ((XTemp + IMAGE_WIDTH) > SCREEN_WIDTH)) { + Entities[i].XDirection = -Entities[i].XDirection; + XTemp = Entities[i].X + Entities[i].XDirection; + } + if ((YTemp < 0) || ((YTemp + IMAGE_HEIGHT) > SCREEN_HEIGHT)) { + Entities[i].YDirection = -Entities[i].YDirection; + YTemp = Entities[i].Y + Entities[i].YDirection; + } + Entities[i].X = XTemp; + Entities[i].Y = YTemp; + } - } while (!kbhit()); - getch(); /* clear the keypress */ - /* Back to text mode */ - regs.x.ax = 0x0003; - int86(0x10, ®s, ®s); - } - /* Draw entities at current locations, updating dirty rectangle list. */ - void DrawEntities() - { - int i, j, k; - char far *RowPtrBuffer; - char far *TempPtrBuffer; - char far *TempPtrImage; - for (i = 0; i < NUM_ENTITIES; i++) { - /* Remember the dirty rectangle info for this entity */ - if (NumDirtyRectangles >= MAX_DIRTY_RECTANGLES) { - /* Too many dirty rectangles; just redraw the whole screen */ - DrawWholeScreen = 1; - } else { - /* Remember this dirty rectangle */ - DirtyRectangles[NumDirtyRectangles].Left = Entities[i].X; - DirtyRectangles[NumDirtyRectangles].Top = Entities[i].Y; - DirtyRectangles[NumDirtyRectangles].Right = - Entities[i].X + IMAGE_WIDTH; - DirtyRectangles[NumDirtyRectangles++].Bottom = - Entities[i].Y + IMAGE_HEIGHT; - } - /* Point to the destination in the system buffer */ - RowPtrBuffer = SystemBufferPtr + (Entities[i].Y * SCREEN_WIDTH) + - Entities[i].X; - /* Point to the image to draw */ - TempPtrImage = ImagePixels; - /* Copy the image to the system buffer */ - for (j = 0; j < IMAGE_HEIGHT; j++) { - /* Copy a row */ - for (k = 0, TempPtrBuffer = RowPtrBuffer; k < IMAGE_WIDTH; k++) { - *TempPtrBuffer++ = *TempPtrImage++; - } - /* Point to the next system buffer row */ - RowPtrBuffer += SCREEN_WIDTH; - } - } - } - /* Copy the dirty rectangles, or the whole system buffer if appropriate, - to the screen. */ - void CopyDirtyRectanglesToScreen() - { - int i, j, k, RectWidth, RectHeight; - unsigned int TempCount; - unsigned int Offset; - char far *TempPtrScreen; - char far *TempPtrBuffer; + } while (!kbhit()); + getch(); /* clear the keypress */ + /* Back to text mode */ + regs.x.ax = 0x0003; + int86(0x10, ®s, ®s); +} +/* Draw entities at current locations, updating dirty rectangle list. */ +void DrawEntities() +{ + int i, j, k; + char far *RowPtrBuffer; + char far *TempPtrBuffer; + char far *TempPtrImage; + for (i = 0; i < NUM_ENTITIES; i++) { + /* Remember the dirty rectangle info for this entity */ + if (NumDirtyRectangles >= MAX_DIRTY_RECTANGLES) { + /* Too many dirty rectangles; just redraw the whole screen */ + DrawWholeScreen = 1; + } else { + /* Remember this dirty rectangle */ + DirtyRectangles[NumDirtyRectangles].Left = Entities[i].X; + DirtyRectangles[NumDirtyRectangles].Top = Entities[i].Y; + DirtyRectangles[NumDirtyRectangles].Right = + Entities[i].X + IMAGE_WIDTH; + DirtyRectangles[NumDirtyRectangles++].Bottom = + Entities[i].Y + IMAGE_HEIGHT; + } + /* Point to the destination in the system buffer */ + RowPtrBuffer = SystemBufferPtr + (Entities[i].Y * SCREEN_WIDTH) + + Entities[i].X; + /* Point to the image to draw */ + TempPtrImage = ImagePixels; + /* Copy the image to the system buffer */ + for (j = 0; j < IMAGE_HEIGHT; j++) { + /* Copy a row */ + for (k = 0, TempPtrBuffer = RowPtrBuffer; k < IMAGE_WIDTH; k++) { + *TempPtrBuffer++ = *TempPtrImage++; + } + /* Point to the next system buffer row */ + RowPtrBuffer += SCREEN_WIDTH; + } + } +} +/* Copy the dirty rectangles, or the whole system buffer if appropriate, + to the screen. */ +void CopyDirtyRectanglesToScreen() +{ + int i, j, k, RectWidth, RectHeight; + unsigned int TempCount; + unsigned int Offset; + char far *TempPtrScreen; + char far *TempPtrBuffer; - if (DrawWholeScreen) { - /* Just copy the whole buffer to the screen */ - DrawWholeScreen = 0; - TempPtrScreen = ScreenPtr; - TempPtrBuffer = SystemBufferPtr; - for (TempCount = ((unsigned)SCREEN_WIDTH*SCREEN_HEIGHT); TempCount--; ) { - *TempPtrScreen++ = *TempPtrBuffer++; - } - } else { - /* Copy only the dirty rectangles */ - for (i = 0; i < NumDirtyRectangles; i++) { - /* Offset in both system buffer and screen of image */ - Offset = (unsigned int) (DirtyRectangles[i].Top * SCREEN_WIDTH) + - DirtyRectangles[i].Left; - /* Dimensions of dirty rectangle */ - RectWidth = DirtyRectangles[i].Right - DirtyRectangles[i].Left; - RectHeight = DirtyRectangles[i].Bottom - DirtyRectangles[i].Top; - /* Copy a dirty rectangle */ - for (j = 0; j < RectHeight; j++) { + if (DrawWholeScreen) { + /* Just copy the whole buffer to the screen */ + DrawWholeScreen = 0; + TempPtrScreen = ScreenPtr; + TempPtrBuffer = SystemBufferPtr; + for (TempCount = ((unsigned)SCREEN_WIDTH*SCREEN_HEIGHT); TempCount--; ) { + *TempPtrScreen++ = *TempPtrBuffer++; + } + } else { + /* Copy only the dirty rectangles */ + for (i = 0; i < NumDirtyRectangles; i++) { + /* Offset in both system buffer and screen of image */ + Offset = (unsigned int) (DirtyRectangles[i].Top * SCREEN_WIDTH) + + DirtyRectangles[i].Left; + /* Dimensions of dirty rectangle */ + RectWidth = DirtyRectangles[i].Right - DirtyRectangles[i].Left; + RectHeight = DirtyRectangles[i].Bottom - DirtyRectangles[i].Top; + /* Copy a dirty rectangle */ + for (j = 0; j < RectHeight; j++) { - /* Point to the start of row on screen */ - TempPtrScreen = ScreenPtr + Offset; + /* Point to the start of row on screen */ + TempPtrScreen = ScreenPtr + Offset; - /* Point to the start of row in system buffer */ - TempPtrBuffer = SystemBufferPtr + Offset; + /* Point to the start of row in system buffer */ + TempPtrBuffer = SystemBufferPtr + Offset; - /* Copy a row */ - for (k = 0; k < RectWidth; k++) { - *TempPtrScreen++ = *TempPtrBuffer++; - } - /* Point to the next row */ - Offset += SCREEN_WIDTH; - } - } - } - } - /* Erase the entities in the system buffer at their current locations, - updating the dirty rectangle list. */ - void EraseEntities() - { - int i, j, k; - char far *RowPtr; - char far *TempPtr; + /* Copy a row */ + for (k = 0; k < RectWidth; k++) { + *TempPtrScreen++ = *TempPtrBuffer++; + } + /* Point to the next row */ + Offset += SCREEN_WIDTH; + } + } + } +} +/* Erase the entities in the system buffer at their current locations, + updating the dirty rectangle list. */ +void EraseEntities() +{ + int i, j, k; + char far *RowPtr; + char far *TempPtr; - for (i = 0; i < NUM_ENTITIES; i++) { - /* Remember the dirty rectangle info for this entity */ - if (NumDirtyRectangles >= MAX_DIRTY_RECTANGLES) { - /* Too many dirty rectangles; just redraw the whole screen */ - DrawWholeScreen = 1; - } else { - /* Remember this dirty rectangle */ - DirtyRectangles[NumDirtyRectangles].Left = Entities[i].X; - DirtyRectangles[NumDirtyRectangles].Top = Entities[i].Y; - DirtyRectangles[NumDirtyRectangles].Right = - Entities[i].X + IMAGE_WIDTH; - DirtyRectangles[NumDirtyRectangles++].Bottom = - Entities[i].Y + IMAGE_HEIGHT; - } - /* Point to the destination in the system buffer */ - RowPtr = SystemBufferPtr + (Entities[i].Y*SCREEN_WIDTH) + Entities[i].X; + for (i = 0; i < NUM_ENTITIES; i++) { + /* Remember the dirty rectangle info for this entity */ + if (NumDirtyRectangles >= MAX_DIRTY_RECTANGLES) { + /* Too many dirty rectangles; just redraw the whole screen */ + DrawWholeScreen = 1; + } else { + /* Remember this dirty rectangle */ + DirtyRectangles[NumDirtyRectangles].Left = Entities[i].X; + DirtyRectangles[NumDirtyRectangles].Top = Entities[i].Y; + DirtyRectangles[NumDirtyRectangles].Right = + Entities[i].X + IMAGE_WIDTH; + DirtyRectangles[NumDirtyRectangles++].Bottom = + Entities[i].Y + IMAGE_HEIGHT; + } + /* Point to the destination in the system buffer */ + RowPtr = SystemBufferPtr + (Entities[i].Y*SCREEN_WIDTH) + Entities[i].X; - /* Clear the entity's rectangle */ - for (j = 0; j < IMAGE_HEIGHT; j++) { - /* Clear a row */ - for (k = 0, TempPtr = RowPtr; k < IMAGE_WIDTH; k++) { - *TempPtr++ = 0; - } - /* Point to the next row */ - RowPtr += SCREEN_WIDTH; - } - } - } + /* Clear the entity's rectangle */ + for (j = 0; j < IMAGE_HEIGHT; j++) { + /* Clear a row */ + for (k = 0, TempPtr = RowPtr; k < IMAGE_WIDTH; k++) { + *TempPtr++ = 0; + } + /* Point to the next row */ + RowPtr += SCREEN_WIDTH; + } + } +} +``` diff --git a/45-04.md b/45-04.md index 7030da2..28a38ec 100644 --- a/45-04.md +++ b/45-04.md @@ -94,34 +94,36 @@ Listing 45.2, which provides mode set code for 640x400 mode. **LISTING 45.2 L45-2.C** - /* Mode set routine for VGA 640x400 16-color mode. Tested with - Borland C++ in C compilation mode. */ +```c +/* Mode set routine for VGA 640x400 16-color mode. Tested with + Borland C++ in C compilation mode. */ - #include +#include - void Set640x400() - { - union REGS regset; +void Set640x400() +{ + union REGS regset; - /* First, set to standard 640x350 mode (mode 10h) */ - regset.x.ax = 0x0010; - int86(0x10, ®set, ®set); + /* First, set to standard 640x350 mode (mode 10h) */ + regset.x.ax = 0x0010; + int86(0x10, ®set, ®set); - /* Modify the sync polarity bits (bits 7 & 6) of the - Miscellaneous Output register (readable at 0x3CC, writable at - 0x3C2) to select the 400-scan-line vertical scanning rate */ - outp(0x3C2, ((inp(0x3CC) & 0x3F) | 0x40)); + /* Modify the sync polarity bits (bits 7 & 6) of the + Miscellaneous Output register (readable at 0x3CC, writable at + 0x3C2) to select the 400-scan-line vertical scanning rate */ + outp(0x3C2, ((inp(0x3CC) & 0x3F) | 0x40)); - /* Now, tweak the registers needed to convert the vertical - timings from 350 to 400 scan lines */ - outpw(0x3D4, 0x9C10); /* adjust the Vertical Sync Start register - for 400 scan lines */ - outpw(0x3D4, 0x8E11); /* adjust the Vertical Sync End register - for 400 scan lines */ - outpw(0x3D4, 0x8F12); /* adjust the Vertical Display End - register for 400 scan lines */ - outpw(0x3D4, 0x9615); /* adjust the Vertical Blank Start - register for 400 scan lines */ - outpw(0x3D4, 0xB916); /* adjust the Vertical Blank End register - for 400 scan lines */ - } + /* Now, tweak the registers needed to convert the vertical + timings from 350 to 400 scan lines */ + outpw(0x3D4, 0x9C10); /* adjust the Vertical Sync Start register + for 400 scan lines */ + outpw(0x3D4, 0x8E11); /* adjust the Vertical Sync End register + for 400 scan lines */ + outpw(0x3D4, 0x8F12); /* adjust the Vertical Display End + register for 400 scan lines */ + outpw(0x3D4, 0x9615); /* adjust the Vertical Blank Start + register for 400 scan lines */ + outpw(0x3D4, 0xB916); /* adjust the Vertical Blank End register + for 400 scan lines */ +} +``` \ No newline at end of file diff --git a/45-05.md b/45-05.md index dc1d6d4..4cb60d4 100644 --- a/45-05.md +++ b/45-05.md @@ -23,77 +23,79 @@ flipping. **LISTING 45.3 L45-3.C** - /* Sample program to exercise VGA 640x400 16-color mode page flipping, by - drawing a horizontal line at the top of page 0 and another at bottom of page 1, - then flipping between them once every 30 frames. Tested with Borland C++, - in C compilation mode. */ +```c +/* Sample program to exercise VGA 640x400 16-color mode page flipping, by + drawing a horizontal line at the top of page 0 and another at bottom of page 1, + then flipping between them once every 30 frames. Tested with Borland C++, + in C compilation mode. */ - #include - #include +#include +#include - #define SCREEN_SEGMENT 0xA000 - #define SCREEN_HEIGHT 400 - #define SCREEN_WIDTH_IN_BYTES 80 - #define INPUT_STATUS_1 0x3DA /* color-mode address of Input Status 1 - register */ - /* The page start addresses must be even multiples of 256, because page - flipping is performed by changing only the upper start address byte */ - #define PAGE_0_START 0 - #define PAGE_1_START (400*SCREEN_WIDTH_IN_BYTES) +#define SCREEN_SEGMENT 0xA000 +#define SCREEN_HEIGHT 400 +#define SCREEN_WIDTH_IN_BYTES 80 +#define INPUT_STATUS_1 0x3DA /* color-mode address of Input Status 1 + register */ +/* The page start addresses must be even multiples of 256, because page + flipping is performed by changing only the upper start address byte */ +#define PAGE_0_START 0 +#define PAGE_1_START (400*SCREEN_WIDTH_IN_BYTES) - void main(void); - void Wait30Frames(void); - extern void Set640x400(void); +void main(void); +void Wait30Frames(void); +extern void Set640x400(void); - void main() - { - int i; - unsigned int far *ScreenPtr; - union REGS regset; +void main() +{ + int i; + unsigned int far *ScreenPtr; + union REGS regset; - Set640x400(); /* set to 640x400 16-color mode */ + Set640x400(); /* set to 640x400 16-color mode */ - /* Point to first line of page 0 and draw a horizontal line across screen */ - FP_SEG(ScreenPtr) = SCREEN_SEGMENT; - FP_OFF(ScreenPtr) = PAGE_0_START; - for (i=0; i<(SCREEN_WIDTH_IN_BYTES/2); i++) *ScreenPtr++ = 0xFFFF; + /* Point to first line of page 0 and draw a horizontal line across screen */ + FP_SEG(ScreenPtr) = SCREEN_SEGMENT; + FP_OFF(ScreenPtr) = PAGE_0_START; + for (i=0; i<(SCREEN_WIDTH_IN_BYTES/2); i++) *ScreenPtr++ = 0xFFFF; - /* Point to last line of page 1 and draw a horizontal line across screen */ - FP_OFF(ScreenPtr) = - PAGE_1_START + ((SCREEN_HEIGHT-1)*SCREEN_WIDTH_IN_BYTES); - for (i=0; i<(SCREEN_WIDTH_IN_BYTES/2); i++) *ScreenPtr++ = 0xFFFF; + /* Point to last line of page 1 and draw a horizontal line across screen */ + FP_OFF(ScreenPtr) = + PAGE_1_START + ((SCREEN_HEIGHT-1)*SCREEN_WIDTH_IN_BYTES); + for (i=0; i<(SCREEN_WIDTH_IN_BYTES/2); i++) *ScreenPtr++ = 0xFFFF; - /* Now flip pages once every 30 frames until a key is pressed */ - do { - Wait30Frames(); + /* Now flip pages once every 30 frames until a key is pressed */ + do { + Wait30Frames(); - /* Flip to page 1 */ - outpw(0x3D4, 0x0C | ((PAGE_1_START >> 8) << 8)); + /* Flip to page 1 */ + outpw(0x3D4, 0x0C | ((PAGE_1_START >> 8) << 8)); - Wait30Frames(); + Wait30Frames(); - /* Flip to page 0 */ - outpw(0x3D4, 0x0C | ((PAGE_0_START >> 8) << 8)); - } while (kbhit() == 0); + /* Flip to page 0 */ + outpw(0x3D4, 0x0C | ((PAGE_0_START >> 8) << 8)); + } while (kbhit() == 0); - getch(); /* clear the key press */ + getch(); /* clear the key press */ - /* Return to text mode and exit */ - regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */ - int86(0x10, ®set, ®set); - } + /* Return to text mode and exit */ + regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */ + int86(0x10, ®set, ®set); +} - void Wait30Frames() - { - int i; +void Wait30Frames() +{ + int i; - for (i=0; i<30; i++) { - /* 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) ; - } - } + for (i=0; i<30; i++) { + /* 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) ; + } +} +``` 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 @@ -117,19 +119,21 @@ calls down the road. **LISTING 45.4 L45-4.C** - /* Function to tell the BIOS to set up properly sized characters for 25 rows of - 16 pixel high text in 640x400 graphics mode. Call immediately after mode set. - Based on a contribution by Bill Lindley. */ +```c +/* Function to tell the BIOS to set up properly sized characters for 25 rows of + 16 pixel high text in 640x400 graphics mode. Call immediately after mode set. + Based on a contribution by Bill Lindley. */ - #include +#include - void Set640x400() - { - union REGS regs; +void Set640x400() +{ + union REGS regs; - regs.h.ah = 0x11; /* character generator function */ - regs.h.al = 0x24; /* use ROM 8x6 character set for graphics */ - regs.h.bl = 2; /* 25 rows */ - int86(0x10, ®s, ®s); /* invoke the BIOS video interrupt - to set up the text */ - } + regs.h.ah = 0x11; /* character generator function */ + regs.h.al = 0x24; /* use ROM 8x6 character set for graphics */ + regs.h.bl = 2; /* 25 rows */ + int86(0x10, ®s, ®s); /* invoke the BIOS video interrupt + to set up the text */ +} +``` diff --git a/46-02.md b/46-02.md index 72a3de9..32fdff9 100644 --- a/46-02.md +++ b/46-02.md @@ -12,519 +12,523 @@ pages: 863-871 **LISTING 46.1 L46-1.C** - /* Sample simple dirty-rectangle animation program, partially optimized and - featuring internal animation, masked images (sprites), and nonoverlapping dirty - rectangle copying. Tested with Borland C++ in the small model. */ +```c +/* Sample simple dirty-rectangle animation program, partially optimized and + featuring internal animation, masked images (sprites), and nonoverlapping dirty + rectangle copying. Tested with Borland C++ in the small model. */ - #include - #include - #include - #include - #include +#include +#include +#include +#include +#include - /* Comment out to disable overlap elimination in the dirty rectangle list. */ - #define CHECK-OVERLAP 1 - #define SCREEN-WIDTH 320 - #define SCREEN-HEIGHT 200 - #define SCREEN-SEGMENT 0xA000 +/* Comment out to disable overlap elimination in the dirty rectangle list. */ +#define CHECK-OVERLAP 1 +#define SCREEN-WIDTH 320 +#define SCREEN-HEIGHT 200 +#define SCREEN-SEGMENT 0xA000 - /* Describes a dirty rectangle */ - typedef struct { - void *Next; /* pointer to next node in linked dirty rect list */ - int Top; - int Left; - int Right; - int Bottom; - } DirtyRectangle; - /* Describes an animated object */ - typedef struct { - int X; /* upper left corner in virtual bitmap */ - int Y; - int XDirection; /* direction and distance of movement */ - int YDirection; - int InternalAnimateCount; /* tracking internal animation state */ - int InternalAnimateMax; /* maximum internal animation state */ - } Entity; - /* storage used for dirty rectangles */ - #define MAX-DIRTY-RECTANGLES 100 - int NumDirtyRectangles; - DirtyRectangle DirtyRectangles[MAX-DIRTY-RECTANGLES]; - /* head/tail of dirty rectangle list */ - DirtyRectangle DirtyHead; - /* 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 */ - #define IMAGE-WIDTH 13 - #define IMAGE-HEIGHT 11 - char ImagePixels0[] = { - 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, - 0, 0, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, - 0, 9, 9, 0, 0,14,14,14, 9, 9, 0, 0, 0, - 9, 9, 0, 0, 0, 0,14,14,14, 9, 9, 0, 0, - 9, 9, 0, 0, 0, 0,14,14,14, 9, 9, 0, 0, - 9, 9,14, 0, 0,14,14,14,14, 9, 9, 0, 0, - 9, 9,14,14,14,14,14,14,14, 9, 9, 0, 0, - 9, 9,14,14,14,14,14,14,14, 9, 9, 0, 0, - 0, 9, 9,14,14,14,14,14, 9, 9, 0, 0, 0, - 0, 0, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, - 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, - }; - char ImageMask0[] = { - 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, - 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, - 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, - 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, - }; - char ImagePixels1[] = { - 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 0, 0, 9, - 0, 0, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, - 0, 9, 9, 0, 0,14,14,14, 9, 9, 9, 9, 0, - 9, 9, 0, 0, 0, 0,14,14,14, 0, 0, 0, 0, - 9, 9, 0, 0, 0, 0,14,14, 0, 0, 0, 0, 0, - 9, 9,14, 0, 0,14,14,14, 0, 0, 0, 0, 0, - 9, 9,14,14,14,14,14,14, 0, 0, 0, 0, 0, - 9, 9,14,14,14,14,14,14,14, 0, 0, 0, 0, - 0, 9, 9,14,14,14,14,14, 9, 9, 9, 9, 0, - 0, 0, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, - 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 0, 9, 9, - }; - char ImageMask1[] = { - 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, - 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, - 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, - 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, - 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, - 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, - }; - /* Pointers to pixel and mask data for various internally animated - versions of our animated image. */ - char * ImagePixelArray[] = {ImagePixels0, ImagePixels1}; - char * ImageMaskArray[] = {ImageMask0, ImageMask1}; - /* Animated entities */ - #define NUM-ENTITIES 15 - Entity Entities[NUM-ENTITIES]; - /* pointer to system buffer into which we'll draw */ - char far *SystemBufferPtr; - /* pointer to screen */ - char far *ScreenPtr; - void EraseEntities(void); - void CopyDirtyRectanglesToScreen(void); - void DrawEntities(void); - void AddDirtyRect(Entity *, int, int); - void DrawMasked(char far *, char *, char *, int, int, int); - void FillRect(char far *, int, int, int, int); - void CopyRect(char far *, char far *, int, int, int, int); +/* Describes a dirty rectangle */ +typedef struct { + void *Next; /* pointer to next node in linked dirty rect list */ + int Top; + int Left; + int Right; + int Bottom; +} DirtyRectangle; +/* Describes an animated object */ +typedef struct { + int X; /* upper left corner in virtual bitmap */ + int Y; + int XDirection; /* direction and distance of movement */ + int YDirection; + int InternalAnimateCount; /* tracking internal animation state */ + int InternalAnimateMax; /* maximum internal animation state */ +} Entity; +/* storage used for dirty rectangles */ +#define MAX-DIRTY-RECTANGLES 100 +int NumDirtyRectangles; +DirtyRectangle DirtyRectangles[MAX-DIRTY-RECTANGLES]; +/* head/tail of dirty rectangle list */ +DirtyRectangle DirtyHead; +/* 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 */ +#define IMAGE-WIDTH 13 +#define IMAGE-HEIGHT 11 +char ImagePixels0[] = { + 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, + 0, 0, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, + 0, 9, 9, 0, 0,14,14,14, 9, 9, 0, 0, 0, + 9, 9, 0, 0, 0, 0,14,14,14, 9, 9, 0, 0, + 9, 9, 0, 0, 0, 0,14,14,14, 9, 9, 0, 0, + 9, 9,14, 0, 0,14,14,14,14, 9, 9, 0, 0, + 9, 9,14,14,14,14,14,14,14, 9, 9, 0, 0, + 9, 9,14,14,14,14,14,14,14, 9, 9, 0, 0, + 0, 9, 9,14,14,14,14,14, 9, 9, 0, 0, 0, + 0, 0, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, + 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, +}; +char ImageMask0[] = { + 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, + 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, +}; +char ImagePixels1[] = { + 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 0, 0, 9, + 0, 0, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, + 0, 9, 9, 0, 0,14,14,14, 9, 9, 9, 9, 0, + 9, 9, 0, 0, 0, 0,14,14,14, 0, 0, 0, 0, + 9, 9, 0, 0, 0, 0,14,14, 0, 0, 0, 0, 0, + 9, 9,14, 0, 0,14,14,14, 0, 0, 0, 0, 0, + 9, 9,14,14,14,14,14,14, 0, 0, 0, 0, 0, + 9, 9,14,14,14,14,14,14,14, 0, 0, 0, 0, + 0, 9, 9,14,14,14,14,14, 9, 9, 9, 9, 0, + 0, 0, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, + 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 0, 9, 9, +}; +char ImageMask1[] = { + 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, + 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, +}; +/* Pointers to pixel and mask data for various internally animated + versions of our animated image. */ +char * ImagePixelArray[] = {ImagePixels0, ImagePixels1}; +char * ImageMaskArray[] = {ImageMask0, ImageMask1}; +/* Animated entities */ +#define NUM-ENTITIES 15 +Entity Entities[NUM-ENTITIES]; +/* pointer to system buffer into which we'll draw */ +char far *SystemBufferPtr; +/* pointer to screen */ +char far *ScreenPtr; +void EraseEntities(void); +void CopyDirtyRectanglesToScreen(void); +void DrawEntities(void); +void AddDirtyRect(Entity *, int, int); +void DrawMasked(char far *, char *, char *, int, int, int); +void FillRect(char far *, int, int, int, int); +void CopyRect(char far *, char far *, int, int, int, int); - void main() - { - int i, XTemp, YTemp; - unsigned int TempCount; - char far *TempPtr; - union REGS regs; - /* 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"); - exit(1); - } - /* Clear the system buffer */ - TempPtr = SystemBufferPtr; - for (TempCount = ((unsigned)SCREEN-WIDTH*SCREEN-HEIGHT); TempCount--; ) { - *TempPtr++ = 0; - } - /* Point to the screen */ - ScreenPtr = MK-FP(SCREEN-SEGMENT, 0); - /* Set up the entities we'll animate, at random locations */ - randomize(); - for (= 0; < NUM-ENTITIES; i++) { - Entities[i].X = random(SCREEN-WIDTH - IMAGE-WIDTH); - Entities[i].Y = random(SCREEN-HEIGHT - IMAGE-HEIGHT); - Entities[i].XDirection = 1; - Entities[i].YDirection = -1; - Entities[i].InternalAnimateCount = & 1; - Entities[i].InternalAnimateMax = 2; - } - /* Set the dirty rectangle list to empty, and set up the head/tail node - as a sentinel */ - NumDirtyRectangles = 0; - DirtyHead.Next = &DirtyHead; - DirtyHead.Top = 0x7FFF; - DirtyHead.Left= 0x7FFF; - DirtyHead.Bottom = 0x7FFF; - DirtyHead.Right = 0x7FFF; - /* Set 320x200 256-color graphics mode */ - regs.x.ax = 0x0013; - int86(0x10, ®s, ®s); - /* Loop and draw until a key is pressed */ - do { - /* Draw the entities to the system buffer at their current locations, - updating the dirty rectangle list */ - DrawEntities(); - /* Draw the dirty rectangles, or the whole system buffer if - appropriate */ - CopyDirtyRectanglesToScreen(); - /* Reset the dirty rectangle list to empty */ - NumDirtyRectangles = 0; - DirtyHead.Next = &DirtyHead; - /* Erase the entities in the system buffer at their old locations, - updating the dirty rectangle list */ - EraseEntities(); - /* Move the entities, bouncing off the edges of the screen */ - for (= 0; < NUM-ENTITIES; i++) { - XTemp = Entities[i].X + Entities[i].XDirection; - YTemp = Entities[i].Y + Entities[i].YDirection; - if ((XTemp < 0) || ((XTemp + IMAGE-WIDTH) > SCREEN-WIDTH)) { - Entities[i].XDirection = -Entities[i].XDirection; - XTemp = Entities[i].X + Entities[i].XDirection; - } - if ((YTemp < 0) || ((YTemp + IMAGE-HEIGHT) > SCREEN-HEIGHT)) { - Entities[i].YDirection = -Entities[i].YDirection; - YTemp = Entities[i].Y + Entities[i].YDirection; - } - Entities[i].X = XTemp; - Entities[i].Y = YTemp; - } - } while (!kbhit()); - getch(); /* clear the keypress */ +void main() +{ + int i, XTemp, YTemp; + unsigned int TempCount; + char far *TempPtr; + union REGS regs; + /* 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"); + exit(1); + } + /* Clear the system buffer */ + TempPtr = SystemBufferPtr; + for (TempCount = ((unsigned)SCREEN-WIDTH*SCREEN-HEIGHT); TempCount--; ) { + *TempPtr++ = 0; + } + /* Point to the screen */ + ScreenPtr = MK-FP(SCREEN-SEGMENT, 0); + /* Set up the entities we'll animate, at random locations */ + randomize(); + for (= 0; < NUM-ENTITIES; i++) { + Entities[i].X = random(SCREEN-WIDTH - IMAGE-WIDTH); + Entities[i].Y = random(SCREEN-HEIGHT - IMAGE-HEIGHT); + Entities[i].XDirection = 1; + Entities[i].YDirection = -1; + Entities[i].InternalAnimateCount = & 1; + Entities[i].InternalAnimateMax = 2; + } + /* Set the dirty rectangle list to empty, and set up the head/tail node + as a sentinel */ + NumDirtyRectangles = 0; + DirtyHead.Next = &DirtyHead; + DirtyHead.Top = 0x7FFF; + DirtyHead.Left= 0x7FFF; + DirtyHead.Bottom = 0x7FFF; + DirtyHead.Right = 0x7FFF; + /* Set 320x200 256-color graphics mode */ + regs.x.ax = 0x0013; + int86(0x10, ®s, ®s); + /* Loop and draw until a key is pressed */ + do { + /* Draw the entities to the system buffer at their current locations, + updating the dirty rectangle list */ + DrawEntities(); + /* Draw the dirty rectangles, or the whole system buffer if + appropriate */ + CopyDirtyRectanglesToScreen(); + /* Reset the dirty rectangle list to empty */ + NumDirtyRectangles = 0; + DirtyHead.Next = &DirtyHead; + /* Erase the entities in the system buffer at their old locations, + updating the dirty rectangle list */ + EraseEntities(); + /* Move the entities, bouncing off the edges of the screen */ + for (= 0; < NUM-ENTITIES; i++) { + XTemp = Entities[i].X + Entities[i].XDirection; + YTemp = Entities[i].Y + Entities[i].YDirection; + if ((XTemp < 0) || ((XTemp + IMAGE-WIDTH) > SCREEN-WIDTH)) { + Entities[i].XDirection = -Entities[i].XDirection; + XTemp = Entities[i].X + Entities[i].XDirection; + } + if ((YTemp < 0) || ((YTemp + IMAGE-HEIGHT) > SCREEN-HEIGHT)) { + Entities[i].YDirection = -Entities[i].YDirection; + YTemp = Entities[i].Y + Entities[i].YDirection; + } + Entities[i].X = XTemp; + Entities[i].Y = YTemp; + } + } while (!kbhit()); + getch(); /* clear the keypress */ - /* Return back to text mode */ - regs.x.ax = 0x0003; - int86(0x10, ®s, ®s); - } - /* Draw entities at their current locations, updating dirty rectangle list. */ - void DrawEntities() - { - int i; - char far *RowPtrBuffer; - char *TempPtrImage; - char *TempPtrMask; - Entity *EntityPtr; + /* Return back to text mode */ + regs.x.ax = 0x0003; + int86(0x10, ®s, ®s); +} +/* Draw entities at their current locations, updating dirty rectangle list. */ +void DrawEntities() +{ + int i; + char far *RowPtrBuffer; + char *TempPtrImage; + char *TempPtrMask; + Entity *EntityPtr; - for (= 0, EntityPtr = Entities; < NUM-ENTITIES; i++, EntityPtr++) { - /* Remember the dirty rectangle info for this entity */ - AddDirtyRect(EntityPtr, IMAGE-HEIGHT, IMAGE-WIDTH); - /* Point to the destination in the system buffer */ - RowPtrBuffer = SystemBufferPtr + (EntityPtr->Y * SCREEN-WIDTH) + - EntityPtr->X; - /* Advance the image animation pointer */ - if (++EntityPtr->InternalAnimateCount >= - EntityPtr->InternalAnimateMax) { - EntityPtr->InternalAnimateCount = 0; - } - /* Point to the image and mask to draw */ - TempPtrImage = ImagePixelArray[EntityPtr->InternalAnimateCount]; - TempPtrMask = ImageMaskArray[EntityPtr->InternalAnimateCount]; - DrawMasked(RowPtrBuffer, TempPtrImage, TempPtrMask, IMAGE-HEIGHT, - IMAGE-WIDTH, SCREEN-WIDTH); - } - } - /* Copy the dirty rectangles, or the whole system buffer if appropriate, - to the screen. */ - void CopyDirtyRectanglesToScreen() - { - int i, RectWidth, RectHeight; - unsigned int Offset; - DirtyRectangle * DirtyPtr; - if (DrawWholeScreen) { - /* Just copy the whole buffer to the screen */ - DrawWholeScreen = 0; - CopyRect(ScreenPtr, SystemBufferPtr, SCREEN-HEIGHT, SCREEN-WIDTH, - SCREEN-WIDTH, SCREEN-WIDTH); - } else { - /* Copy only the dirty rectangles, in the YX-sorted order in which - they're linked */ - DirtyPtr = DirtyHead.Next; - for (= 0; < NumDirtyRectangles; i++) { - /* Offset in both system buffer and screen of image */ - Offset = (unsigned int) (DirtyPtr->Top * SCREEN-WIDTH) + - DirtyPtr->Left; - /* Dimensions of dirty rectangle */ - RectWidth = DirtyPtr->Right - DirtyPtr->Left; - RectHeight = DirtyPtr->Bottom - DirtyPtr->Top; - /* Copy a dirty rectangle */ - CopyRect(ScreenPtr + Offset, SystemBufferPtr + Offset, - RectHeight, RectWidth, SCREEN-WIDTH, SCREEN-WIDTH); - /* Point to the next dirty rectangle */ - DirtyPtr = DirtyPtr->Next; - } - } - } - /* Erase the entities in the system buffer at their current locations, - updating the dirty rectangle list. */ - void EraseEntities() - { - int i; - char far *RowPtr; - - for (= 0; < NUM-ENTITIES; i++) { - /* Remember the dirty rectangle info for this entity */ - AddDirtyRect(&Entities[i], IMAGE-HEIGHT, IMAGE-WIDTH); - /* Point to the destination in the system buffer */ - RowPtr = SystemBufferPtr + (Entities[i].Y * SCREEN-WIDTH) + - Entities[i].X; - /* Clear the rectangle */ - FillRect(RowPtr, IMAGE-HEIGHT, IMAGE-WIDTH, SCREEN-WIDTH, 0); - } - } - /* Add a dirty rectangle to the list. The list is maintained in top-to-bottom, - left-to-right (YX sorted) order, with no pixel ever included twice, to minimize - the number of display memory accesses and to avoid screen artifacts resulting - from a large time interval between erasure and redraw for a given object or for - adjacent objects. The technique used is to check for overlap between the - rectangle and all rectangles already in the list. If no overlap is found, the - rectangle is added to the list. If overlap is found, the rectangle is broken - into nonoverlapping pieces, and the pieces are added to the list by recursive - calls to this function. */ - void AddDirtyRect(Entity * pEntity, int ImageHeight, int ImageWidth) - { - DirtyRectangle * DirtyPtr; - DirtyRectangle * TempPtr; - Entity TempEntity; - int i; - if (NumDirtyRectangles >= MAX-DIRTY-RECTANGLES) { - /* Too many dirty rectangles; just redraw the whole screen */ - DrawWholeScreen = 1; - return; - } - /* Remember this dirty rectangle. Break up if necessary to avoid - overlap with rectangles already in the list, then add whatever - rectangles are left, in YX sorted order */ - #ifdef CHECK-OVERLAP - /* Check for overlap with existing rectangles */ - TempPtr = DirtyHead.Next; - for (= 0; < NumDirtyRectangles; i++, TempPtr = TempPtr->Next) { - if ((TempPtr->Left < (pEntity->X + ImageWidth)) && - (TempPtr->Right > pEntity->X) && - (TempPtr->Top < (pEntity->Y + ImageHeight)) && - (TempPtr->Bottom > pEntity->Y)) { + for (= 0, EntityPtr = Entities; < NUM-ENTITIES; i++, EntityPtr++) { + /* Remember the dirty rectangle info for this entity */ + AddDirtyRect(EntityPtr, IMAGE-HEIGHT, IMAGE-WIDTH); + /* Point to the destination in the system buffer */ + RowPtrBuffer = SystemBufferPtr + (EntityPtr->Y * SCREEN-WIDTH) + + EntityPtr->X; + /* Advance the image animation pointer */ + if (++EntityPtr->InternalAnimateCount >= + EntityPtr->InternalAnimateMax) { + EntityPtr->InternalAnimateCount = 0; + } + /* Point to the image and mask to draw */ + TempPtrImage = ImagePixelArray[EntityPtr->InternalAnimateCount]; + TempPtrMask = ImageMaskArray[EntityPtr->InternalAnimateCount]; + DrawMasked(RowPtrBuffer, TempPtrImage, TempPtrMask, IMAGE-HEIGHT, + IMAGE-WIDTH, SCREEN-WIDTH); + } +} +/* Copy the dirty rectangles, or the whole system buffer if appropriate, + to the screen. */ +void CopyDirtyRectanglesToScreen() +{ + int i, RectWidth, RectHeight; + unsigned int Offset; + DirtyRectangle * DirtyPtr; + if (DrawWholeScreen) { + /* Just copy the whole buffer to the screen */ + DrawWholeScreen = 0; + CopyRect(ScreenPtr, SystemBufferPtr, SCREEN-HEIGHT, SCREEN-WIDTH, + SCREEN-WIDTH, SCREEN-WIDTH); + } else { + /* Copy only the dirty rectangles, in the YX-sorted order in which + they're linked */ + DirtyPtr = DirtyHead.Next; + for (= 0; < NumDirtyRectangles; i++) { + /* Offset in both system buffer and screen of image */ + Offset = (unsigned int) (DirtyPtr->Top * SCREEN-WIDTH) + + DirtyPtr->Left; + /* Dimensions of dirty rectangle */ + RectWidth = DirtyPtr->Right - DirtyPtr->Left; + RectHeight = DirtyPtr->Bottom - DirtyPtr->Top; + /* Copy a dirty rectangle */ + CopyRect(ScreenPtr + Offset, SystemBufferPtr + Offset, + RectHeight, RectWidth, SCREEN-WIDTH, SCREEN-WIDTH); + /* Point to the next dirty rectangle */ + DirtyPtr = DirtyPtr->Next; + } + } +} +/* Erase the entities in the system buffer at their current locations, + updating the dirty rectangle list. */ +void EraseEntities() +{ + int i; + char far *RowPtr; + + for (= 0; < NUM-ENTITIES; i++) { + /* Remember the dirty rectangle info for this entity */ + AddDirtyRect(&Entities[i], IMAGE-HEIGHT, IMAGE-WIDTH); + /* Point to the destination in the system buffer */ + RowPtr = SystemBufferPtr + (Entities[i].Y * SCREEN-WIDTH) + + Entities[i].X; + /* Clear the rectangle */ + FillRect(RowPtr, IMAGE-HEIGHT, IMAGE-WIDTH, SCREEN-WIDTH, 0); + } +} +/* Add a dirty rectangle to the list. The list is maintained in top-to-bottom, + left-to-right (YX sorted) order, with no pixel ever included twice, to minimize + the number of display memory accesses and to avoid screen artifacts resulting + from a large time interval between erasure and redraw for a given object or for + adjacent objects. The technique used is to check for overlap between the + rectangle and all rectangles already in the list. If no overlap is found, the + rectangle is added to the list. If overlap is found, the rectangle is broken + into nonoverlapping pieces, and the pieces are added to the list by recursive + calls to this function. */ + void AddDirtyRect(Entity * pEntity, int ImageHeight, int ImageWidth) +{ + DirtyRectangle * DirtyPtr; + DirtyRectangle * TempPtr; + Entity TempEntity; + int i; + if (NumDirtyRectangles >= MAX-DIRTY-RECTANGLES) { + /* Too many dirty rectangles; just redraw the whole screen */ + DrawWholeScreen = 1; + return; + } + /* Remember this dirty rectangle. Break up if necessary to avoid + overlap with rectangles already in the list, then add whatever + rectangles are left, in YX sorted order */ +#ifdef CHECK-OVERLAP + /* Check for overlap with existing rectangles */ + TempPtr = DirtyHead.Next; + for (= 0; < NumDirtyRectangles; i++, TempPtr = TempPtr->Next) { + if ((TempPtr->Left < (pEntity->X + ImageWidth)) && + (TempPtr->Right > pEntity->X) && + (TempPtr->Top < (pEntity->Y + ImageHeight)) && + (TempPtr->Bottom > pEntity->Y)) { - /* 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 - 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; - TempEntity.Y = max(pEntity->Y, TempPtr->Top); - AddDirtyRect(&TempEntity, - min(pEntity->Y + ImageHeight, TempPtr->Bottom) - - TempEntity.Y, - TempPtr->Left - pEntity->X); - } - /* Check for a nonoverlapped right portion */ - if (TempPtr->Right < (pEntity->X + ImageWidth)) { - /* 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; - TempEntity.Y = max(pEntity->Y, TempPtr->Top); - AddDirtyRect(&TempEntity, - min(pEntity->Y + ImageHeight, TempPtr->Bottom) - - TempEntity.Y, - (pEntity->X + ImageWidth) - TempPtr->Right); - } - /* Check for a nonoverlapped top portion */ - if (TempPtr->Top > pEntity->Y) { - /* 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 */ - 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 */ - return; - } - } - #endif /* CHECK-OVERLAP */ - /* 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 */ - TempPtr = &DirtyHead; - while (((DirtyRectangle *)TempPtr->Next)->Top < pEntity->Y) { - TempPtr = TempPtr->Next; - } - while ((((DirtyRectangle *)TempPtr->Next)->Top == pEntity->Y) && - (((DirtyRectangle *)TempPtr->Next)->Left < pEntity->X)) { - TempPtr = TempPtr->Next; - } - /* Set the rectangle and actually add it to the dirty list */ - DirtyPtr = &DirtyRectangles[NumDirtyRectangles++]; - DirtyPtr->Left = pEntity->X; - DirtyPtr->Top = pEntity->Y; - DirtyPtr->Right = pEntity->X + ImageWidth; - DirtyPtr->Bottom = pEntity->Y + ImageHeight; - DirtyPtr->Next = TempPtr->Next; - TempPtr->Next = DirtyPtr; - } + /* 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 + 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; + TempEntity.Y = max(pEntity->Y, TempPtr->Top); + AddDirtyRect(&TempEntity, + min(pEntity->Y + ImageHeight, TempPtr->Bottom) - + TempEntity.Y, + TempPtr->Left - pEntity->X); + } + /* Check for a nonoverlapped right portion */ + if (TempPtr->Right < (pEntity->X + ImageWidth)) { + /* 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; + TempEntity.Y = max(pEntity->Y, TempPtr->Top); + AddDirtyRect(&TempEntity, + min(pEntity->Y + ImageHeight, TempPtr->Bottom) - + TempEntity.Y, + (pEntity->X + ImageWidth) - TempPtr->Right); + } + /* Check for a nonoverlapped top portion */ + if (TempPtr->Top > pEntity->Y) { + /* 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 */ + 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 */ + return; + } + } +#endif /* CHECK-OVERLAP */ + /* 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 */ + TempPtr = &DirtyHead; + while (((DirtyRectangle *)TempPtr->Next)->Top < pEntity->Y) { + TempPtr = TempPtr->Next; + } + while ((((DirtyRectangle *)TempPtr->Next)->Top == pEntity->Y) && + (((DirtyRectangle *)TempPtr->Next)->Left < pEntity->X)) { + TempPtr = TempPtr->Next; + } + /* Set the rectangle and actually add it to the dirty list */ + DirtyPtr = &DirtyRectangles[NumDirtyRectangles++]; + DirtyPtr->Left = pEntity->X; + DirtyPtr->Top = pEntity->Y; + DirtyPtr->Right = pEntity->X + ImageWidth; + DirtyPtr->Bottom = pEntity->Y + ImageHeight; + DirtyPtr->Next = TempPtr->Next; + TempPtr->Next = DirtyPtr; +} +``` **LISTING 46.2 L46-2.ASM** - ; Assembly language helper routines for dirty rectangle animation. Tested with - ; TASM. - ; Fills a rectangle in the specified buffer. - ; C-callable as: - ; void FillRect(char far * BufferPtr, int RectHeight, int RectWidth, - ; int BufferWidth, int Color); - ; - .model small - .code - parms struc - dw ? ;pushed BP - dw ? ;pushed return address - BufferPtr dd ? ;far pointer to buffer in which to fill - RectHeight dw ? ;height of rectangle to fill - RectWidth dw ? ;width of rectangle to fill - BufferWidth dw ? ;width of buffer in which to fill - Color dw ? ;color with which to fill - parms ends - public -FillRect - -FillRectproc near - cld - push bp - mov bp,sp - push di +```nasm +; Assembly language helper routines for dirty rectangle animation. Tested with +; TASM. +; Fills a rectangle in the specified buffer. +; C-callable as: +; void FillRect(char far * BufferPtr, int RectHeight, int RectWidth, +; int BufferWidth, int Color); +; + .model small + .code +parms struc + dw ? ;pushed BP + dw ? ;pushed return address +BufferPtr dd ? ;far pointer to buffer in which to fill +RectHeight dw ? ;height of rectangle to fill +RectWidth dw ? ;width of rectangle to fill +BufferWidth dw ? ;width of buffer in which to fill +Color dw ? ;color with which to fill +parms ends + public -FillRect +-FillRectproc near + cld + push bp + mov bp,sp + push di - les di,[bp+BufferPtr] - mov dx,[bp+RectHeight] - mov bx,[bp+BufferWidth] - su bx,[bp+RectWidth] ;distance from end of one dest scan - ; to start of next - mov al,byte ptr [bp+Color] - mov ah,al ;double the color for REP STOSW - RowLoop: - mov cx,[bp+RectWidth] - shr cx,1 - rep stosw - adc cx,cx - rep stosb - add di,bx ;point to next scan to fill - dec dx ;count down rows to fill - jnz RowLoop + les di,[bp+BufferPtr] + mov dx,[bp+RectHeight] + mov bx,[bp+BufferWidth] + su bx,[bp+RectWidth] ;distance from end of one dest scan + ; to start of next + mov al,byte ptr [bp+Color] + mov ah,al ;double the color for REP STOSW +RowLoop: + mov cx,[bp+RectWidth] + shr cx,1 + rep stosw + adc cx,cx + rep stosb + add di,bx ;point to next scan to fill + dec dx ;count down rows to fill + jnz RowLoop - pop di - pop bp - ret - -FillRect endp + pop di + pop bp + ret +-FillRect endp - ; Draws a masked image (a sprite) to the specified buffer. C-callable as: - ; void DrawMasked(char far * BufferPtr, char * Pixels, char * Mask, - ; int ImageHeight, int ImageWidth, int BufferWidth); - parms2 struc - dw ? ;pushed BP - dw ? ;pushed return address - BufferPtr2 dd ? ;far pointer to buffer in which to draw - Pixels dw ? ;pointer to image pixels - Mask dw ? ;pointer to image mask - ImageHeight dw ? ;height of image to draw - ImageWidth dw ? ;width of image to draw - BufferWidth2 dw ? ;width of buffer in which to draw - parms2 ends - public -DrawMasked - -DrawMasked proc near - cld - push bp - mov bp,sp - push si - push di +; Draws a masked image (a sprite) to the specified buffer. C-callable as: +; void DrawMasked(char far * BufferPtr, char * Pixels, char * Mask, +; int ImageHeight, int ImageWidth, int BufferWidth); +parms2 struc + dw ? ;pushed BP + dw ? ;pushed return address +BufferPtr2 dd ? ;far pointer to buffer in which to draw +Pixels dw ? ;pointer to image pixels +Mask dw ? ;pointer to image mask +ImageHeight dw ? ;height of image to draw +ImageWidth dw ? ;width of image to draw +BufferWidth2 dw ? ;width of buffer in which to draw +parms2 ends + public -DrawMasked +-DrawMasked proc near + cld + push bp + mov bp,sp + push si + push di - les di,[bp+BufferPtr2] - mov si,[bp+Mask] - mov bx,[bp+Pixels] - mov dx,[bp+ImageHeight] - mov ax,[bp+BufferWidth2] - su ax,[bp+ImageWidth] ;distance from end of one dest scan - mov [bp+BufferWidth2],ax ; to start of next - RowLoop2: - mov cx,[bp+ImageWidth] - ColumnLoop: - lods ;get the next mask byte - and al,al ;draw this pixel? - jz SkipPixel ;no - mov al,[bx] ;yes, draw the pixel - mov es:[di],al - SkipPixel: - inc bx ;point to next source pixel - inc d ;point to next dest pixel - dec cx - jnz ColumnLoop - add di,[bp+BufferWidth2] ;point to next scan to fill - dec dx ;count down rows to fill - jnz RowLoop2 + les di,[bp+BufferPtr2] + mov si,[bp+Mask] + mov bx,[bp+Pixels] + mov dx,[bp+ImageHeight] + mov ax,[bp+BufferWidth2] + su ax,[bp+ImageWidth] ;distance from end of one dest scan + mov [bp+BufferWidth2],ax ; to start of next +RowLoop2: + mov cx,[bp+ImageWidth] +ColumnLoop: + lods ;get the next mask byte + and al,al ;draw this pixel? + jz SkipPixel ;no + mov al,[bx] ;yes, draw the pixel + mov es:[di],al +SkipPixel: + inc bx ;point to next source pixel + inc d ;point to next dest pixel + dec cx + jnz ColumnLoop + add di,[bp+BufferWidth2] ;point to next scan to fill + dec dx ;count down rows to fill + jnz RowLoop2 - pop di - pop si - pop bp - ret - -DrawMasked endp + pop di + pop si + pop bp + ret +-DrawMasked endp - ; Copies a rectangle from one buffer to another. C-callable as: - ; void CopyRect(DestBufferPtr, SrcBufferPtr, CopyHeight, CopyWidth, - ; DestBufferWidth, SrcBufferWidth); +; Copies a rectangle from one buffer to another. C-callable as: +; void CopyRect(DestBufferPtr, SrcBufferPtr, CopyHeight, CopyWidth, +; DestBufferWidth, SrcBufferWidth); - parms3 struc - dw ? ;pushed BP - dw ? ;pushed return address - DestBufferPtr dd ? ;far pointer to buffer to which to copy - SrcBufferPtr dd ? ;far pointer to buffer from which to copy - CopyHeight dw ? ;height of rect to copy - CopyWidth dw ? ;width of rect to copy - DestBufferWidth dw ? ;width of buffer to which to copy - SrcBufferWidth dw ? ;width of buffer from which to copy - parms3 ends - public -CopyRect - -CopyRect proc near - cld - push bp - mov bp,sp - push si - push di - push ds +parms3 struc + dw ? ;pushed BP + dw ? ;pushed return address +DestBufferPtr dd ? ;far pointer to buffer to which to copy +SrcBufferPtr dd ? ;far pointer to buffer from which to copy +CopyHeight dw ? ;height of rect to copy +CopyWidth dw ? ;width of rect to copy +DestBufferWidth dw ? ;width of buffer to which to copy +SrcBufferWidth dw ? ;width of buffer from which to copy +parms3 ends + public -CopyRect +-CopyRect proc near + cld + push bp + mov bp,sp + push si + push di + push ds - les di,[bp+DestBufferPtr] - lds si,[bp+SrcBufferPtr] - mov dx,[bp+CopyHeight] - mov bx,[bp+DestBufferWidth] ;distance from end of one dest scan - su bx,[bp+CopyWidth] ; of copy to the next - mov ax,[bp+SrcBufferWidth] ;distance from end of one source scan - su ax,[bp+CopyWidth] ; of copy to the next - RowLoop3: - mov cx,[bp+CopyWidth] ;# of bytes to copy - shr cx,1 - rep movsw ;copy as many words as possible - adc cx,cx - rep movs ;copy odd byte, if any - add si,ax ;point to next source scan line - add di,bx ;point to next dest scan line - dec dx ;count down rows to fill - jnz RowLoop3 + les di,[bp+DestBufferPtr] + lds si,[bp+SrcBufferPtr] + mov dx,[bp+CopyHeight] + mov bx,[bp+DestBufferWidth] ;distance from end of one dest scan + su bx,[bp+CopyWidth] ; of copy to the next + mov ax,[bp+SrcBufferWidth] ;distance from end of one source scan + su ax,[bp+CopyWidth] ; of copy to the next +RowLoop3: + mov cx,[bp+CopyWidth] ;# of bytes to copy + shr cx,1 + rep movsw ;copy as many words as possible + adc cx,cx + rep movs ;copy odd byte, if any + add si,ax ;point to next source scan line + add di,bx ;point to next dest scan line + dec dx ;count down rows to fill + jnz RowLoop3 - pop ds - pop di - pop si - pop bp - ret - -CopyRect endp - end + pop ds + pop di + pop si + pop bp + ret +-CopyRect endp + end +``` diff --git a/47-02.md b/47-02.md index 9d48cff..25dcdf9 100644 --- a/47-02.md +++ b/47-02.md @@ -70,91 +70,93 @@ pay for the unfettered creativity and vast choice of the PC market. **LISTING 47.1 L47-1.ASM** - ; Mode X (320x240, 256 colors) mode set routine. Works on all VGAs. - ; **************************************************************** - ; * Revised 6/19/91 to select correct clock; fixes vertical roll * - ; * problems on fixed-frequency (IBM 851X-type) monitors. * - ; **************************************************************** - ; C near-callable as: - ; void Set320x240Mode(void); - ; Tested with TASM - ; Modified from public-domain mode set code by John Bridges. +```nasm +; Mode X (320x240, 256 colors) mode set routine. Works on all VGAs. +; **************************************************************** +; * Revised 6/19/91 to select correct clock; fixes vertical roll * +; * problems on fixed-frequency (IBM 851X-type) monitors. * +; **************************************************************** +; C near-callable as: +; void Set320x240Mode(void); +; Tested with TASM +; Modified from public-domain mode set code by John Bridges. - SC_INDEX equ 03c4h ;Sequence Controller Index - CRTC_INDEX equ 03d4h ;CRT Controller Index - MISC_OUTPUT equ 03c2h ;Miscellaneous Output register - SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SC_INDEX equ 03c4h ;Sequence Controller Index +CRTC_INDEX equ 03d4h ;CRT Controller Index +MISC_OUTPUT equ 03c2h ;Miscellaneous Output register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X - .model small - .data - ; Index/data pairs for CRT Controller registers that differ between - ; mode 13h and mode X. - CRTParms label word - dw 00d06h ;vertical total - dw 03e07h ;overflow (bit 8 of vertical counts) - dw 04109h ;cell height (2 to double-scan) - dw 0ea10h ;v sync start - dw 0ac11h ;v sync end and protect cr0-cr7 - dw 0df12h ;vertical displayed - dw 00014h ;turn off dword mode - dw 0e715h ;v blank start - dw 00616h ;v blank end - dw 0e317h ;turn on byte mode - CRT_PARM_LENGTH equ (($-CRTParms)/2) + .model small + .data +; Index/data pairs for CRT Controller registers that differ between +; mode 13h and mode X. +CRTParms label word + dw 00d06h ;vertical total + dw 03e07h ;overflow (bit 8 of vertical counts) + dw 04109h ;cell height (2 to double-scan) + dw 0ea10h ;v sync start + dw 0ac11h ;v sync end and protect cr0-cr7 + dw 0df12h ;vertical displayed + dw 00014h ;turn off dword mode + dw 0e715h ;v blank start + dw 00616h ;v blank end + dw 0e317h ;turn on byte mode +CRT_PARM_LENGTH equ (($-CRTParms)/2) - .code - public _Set320x240Mode - _Set320x240Mode proc near - push bp ;preserve caller's stack frame - push si ;preserve C register vars - push di ; (don't count on BIOS preserving anything) + .code + public _Set320x240Mode +_Set320x240Mode proc near + push bp ;preserve caller's stack frame + push si ;preserve C register vars + push di ; (don't count on BIOS preserving anything) - mov ax,13h ;let the BIOS set standard 256-color - int 10h ; mode (320x200 linear) + mov ax,13h ;let the BIOS set standard 256-color + int 10h ; mode (320x200 linear) - mov dx,SC_INDEX - mov ax,0604h - out dx,ax ;disable chain4 mode - mov ax,0100h - out dx,ax ;synchronous reset while setting Misc Output - ; for safety, even though clock unchanged - mov dx,MISC_OUTPUT - mov al,0e3h - out dx,al ;select 25 MHz dot clock & 60 Hz scanning rate + mov dx,SC_INDEX + mov ax,0604h + out dx,ax ;disable chain4 mode + mov ax,0100h + out dx,ax ;synchronous reset while setting Misc Output + ; for safety, even though clock unchanged + mov dx,MISC_OUTPUT + mov al,0e3h + out dx,al ;select 25 MHz dot clock & 60 Hz scanning rate - mov dx,SC_INDEX - mov ax,0300h - out dx,ax ;undo reset (restart sequencer) + mov dx,SC_INDEX + mov ax,0300h + out dx,ax ;undo reset (restart sequencer) - mov dx,CRTC_INDEX ;reprogram the CRT Controller - mov al,11h ;VSync End reg contains register write - out dx,al ; protect bit - inc dx ;CRT Controller Data register - in al,dx ;get current VSync End register setting - and al,7fh ;remove write protect on various - out dx,al ; CRTC registers - dec dx ;CRT Controller Index - cld - mov si,offset CRTParms ;point to CRT parameter table - mov cx,CRT_PARM_LENGTH ;# of table entries - SetCRTParmsLoop: - lodsw ;get the next CRT Index/Data pair - out dx,ax ;set the next CRT Index/Data pair - loop SetCRTParmsLoop + mov dx,CRTC_INDEX ;reprogram the CRT Controller + mov al,11h ;VSync End reg contains register write + out dx,al ; protect bit + inc dx ;CRT Controller Data register + in al,dx ;get current VSync End register setting + and al,7fh ;remove write protect on various + out dx,al ; CRTC registers + dec dx ;CRT Controller Index + cld + mov si,offset CRTParms ;point to CRT parameter table + mov cx,CRT_PARM_LENGTH ;# of table entries +SetCRTParmsLoop: + lodsw ;get the next CRT Index/Data pair + out dx,ax ;set the next CRT Index/Data pair + loop SetCRTParmsLoop - mov dx,SC_INDEX - mov ax,0f02h - out dx,ax ;enable writes to all four planes - mov ax,SCREEN_SEG ;now clear all display memory, 8 pixels - mov es,ax ; at a time - sub di,di ;point ES:DI to display memory - sub ax,ax ;clear to zero-value pixels - mov cx,8000h ;# of words in display memory - rep stosw ;clear all of display memory + mov dx,SC_INDEX + mov ax,0f02h + out dx,ax ;enable writes to all four planes + mov ax,SCREEN_SEG ;now clear all display memory, 8 pixels + mov es,ax ; at a time + sub di,di ;point ES:DI to display memory + sub ax,ax ;clear to zero-value pixels + mov cx,8000h ;# of words in display memory + rep stosw ;clear all of display memory - pop di ;restore C register vars - pop si - pop bp ;restore caller's stack frame - ret - _Set320x240Mode endp - end + pop di ;restore C register vars + pop si + pop bp ;restore caller's stack frame + ret +_Set320x240Mode endp + end +``` diff --git a/47-03.md b/47-03.md index 4b87fbc..bc87ef6 100644 --- a/47-03.md +++ b/47-03.md @@ -60,107 +60,111 @@ are fills, copies, and bitblts, and it's there that Mode X shines. **LISTING 47.2 L47-2.ASM** - ; Mode X (320x240, 256 colors) write pixel routine. Works on all VGAs. - ; No clipping is performed. - ; C near-callable as: - ; - ; void WritePixelX(int X, int Y, unsigned int PageBase, int Color); +```nasm +; Mode X (320x240, 256 colors) write pixel routine. Works on all VGAs. +; No clipping is performed. +; C near-callable as: +; +; void WritePixelX(int X, int Y, unsigned int PageBase, int Color); - SC_INDEX equ 03c4h ;Sequence Controller Index - MAP_MASK equ 02h ;index in SC of Map Mask register - SCREEN_SEG equ 0a000h ;segment of display memory in mode X - SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line - ; to the next +SC_INDEX equ 03c4h ;Sequence Controller Index +MAP_MASK equ 02h ;index in SC of Map Mask register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line + ; to the next - parms struc - dw 2 dup (?) ;pushed BP and return address - X dw ? ;X coordinate of pixel to draw - Y dw ? ;Y coordinate of pixel to draw - PageBase dw ? ;base offset in display memory of page in - ; which to draw pixel - Color dw ? ;color in which to draw pixel - parms ends +parms struc + dw 2 dup (?) ;pushed BP and return address +X dw ? ;X coordinate of pixel to draw +Y dw ? ;Y coordinate of pixel to draw +PageBase dw ? ;base offset in display memory of page in + ; which to draw pixel +Color dw ? ;color in which to draw pixel +parms ends - .model small - .code - public _WritePixelX - _WritePixelX proc near - push bp ;preserve caller's stack frame - mov bp,sp ;point to local stack frame + .model small + .code + public _WritePixelX +_WritePixelX proc near + 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 - 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 ax,SCREEN_WIDTH + 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 cl,byte ptr [bp+X] - 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 - mov dx,SC_INDEX ;set the Map Mask to enable only the - out dx,ax ; pixel's plane + mov cl,byte ptr [bp+X] + 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 + mov dx,SC_INDEX ;set the Map Mask to enable only the + out dx,ax ; pixel's plane - mov al,byte ptr [bp+Color] - mov es:[bx],al ;draw the pixel in the desired color + mov al,byte ptr [bp+Color] + mov es:[bx],al ;draw the pixel in the desired color - pop bp ;restore caller's stack frame - ret - _WritePixelX endp - end + pop bp ;restore caller's stack frame + ret +_WritePixelX endp + end +``` **LISTING 47.3 L47-3.ASM** - ; Mode X (320x240, 256 colors) read pixel routine. Works on all VGAs. - ; No clipping is performed. - ; C near-callable as: - ; - ; unsigned int ReadPixelX(int X, int Y, unsigned int PageBase); +```nasm +; Mode X (320x240, 256 colors) read pixel routine. Works on all VGAs. +; No clipping is performed. +; C near-callable as: +; +; unsigned int ReadPixelX(int X, int Y, unsigned int PageBase); - GC_INDEX equ 03ceh ;Graphics Controller Index - READ_MAP equ 04h ;index in GC of the Read Map register - SCREEN_SEG equ 0a000h ;segment of display memory in mode X - SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line - ; to the next - parms struc - dw 2 dup (?) ;pushed BP and return address - X dw ? ;X coordinate of pixel to read - Y dw ? ;Y coordinate of pixel to read - PageBase dw ? ;base offset in display memory of page from - ; which to read pixel - parms ends +GC_INDEX equ 03ceh ;Graphics Controller Index +READ_MAP equ 04h ;index in GC of the Read Map register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line + ; to the next +parms struc + dw 2 dup (?) ;pushed BP and return address +X dw ? ;X coordinate of pixel to read +Y dw ? ;Y coordinate of pixel to read +PageBase dw ? ;base offset in display memory of page from + ; which to read pixel +parms ends - .model small - .code - public _ReadPixelX - _ReadPixelX proc near - push bp ;preserve caller's stack frame - mov bp,sp ;point to local stack frame + .model small + .code + public _ReadPixelX +_ReadPixelX proc near + 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 - 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 ax,SCREEN_WIDTH + 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 ah,byte ptr [bp+X] - 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 - out dx,ax ; plane + mov ah,byte ptr [bp+X] + 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 + out dx,ax ; plane - mov al,es:[bx] ;read the pixel's color - sub ah,ah ;convert it to an unsigned int + 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 - ret - _ReadPixelX endp - end + pop bp ;restore caller's stack frame + ret +_ReadPixelX endp + end +``` diff --git a/47-04.md b/47-04.md index dce6132..157a379 100644 --- a/47-04.md +++ b/47-04.md @@ -24,96 +24,98 @@ that's possible with code that's designed from a Mode X perspective. **LISTING 47.4 L47-4.ASM** - ; Mode X (320x240, 256 colors) rectangle fill routine. Works on all - ; VGAs. Uses slow approach that selects the plane explicitly for each - ; pixel. Fills up to but not including the column at EndX and the row - ; at EndY. No clipping is performed. - ; C near-callable as: - ; - ; void FillRectangleX(int StartX, int StartY, int EndX, int EndY, - ; unsigned int PageBase, int Color); +```nasm +; Mode X (320x240, 256 colors) rectangle fill routine. Works on all +; VGAs. Uses slow approach that selects the plane explicitly for each +; pixel. Fills up to but not including the column at EndX and the row +; at EndY. No clipping is performed. +; C near-callable as: +; +; void FillRectangleX(int StartX, int StartY, int EndX, int EndY, +; unsigned int PageBase, int Color); - SC_INDEX equ 03c4h ;Sequence Controller Index - MAP_MASK equ 02h ;index in SC of Map Mask register - SCREEN_SEG equ 0a000h ;segment of display memory in mode X - SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line - ; to the next - parms struc - dw 2 dup (?) ;pushed BP and return address - StartX dw ? ;X coordinate of upper left corner of rect - StartY dw ? ;Y coordinate of upper left corner of rect - EndX dw ? ;X coordinate of lower right corner of rect - ; (the row at EndX is not filled) - EndY dw ? ;Y coordinate of lower right corner of rect - ; (the column at EndY is not filled) - PageBase dw ? ;base offset in display memory of page in - ; which to fill rectangle - Color dw ? ;color in which to draw pixel - parms ends +SC_INDEX equ 03c4h ;Sequence Controller Index +MAP_MASK equ 02h ;index in SC of Map Mask register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line + ; to the next +parms struc + dw 2 dup (?) ;pushed BP and return address +StartX dw ? ;X coordinate of upper left corner of rect +StartY dw ? ;Y coordinate of upper left corner of rect +EndX dw ? ;X coordinate of lower right corner of rect + ; (the row at EndX is not filled) +EndY dw ? ;Y coordinate of lower right corner of rect + ; (the column at EndY is not filled) +PageBase dw ? ;base offset in display memory of page in + ; which to fill rectangle +Color dw ? ;color in which to draw pixel +parms ends - .model small - .code - public _FillRectangleX - _FillRectangleX proc near - push bp ;preserve caller's stack frame - mov bp,sp ;point to local stack frame - push si ;preserve caller's register variables - push di + .model small + .code + public _FillRectangleX +_FillRectangleX proc near + push bp ;preserve caller's stack frame + mov bp,sp ;point to local stack frame + push si ;preserve caller's register variables + push di - mov ax,SCREEN_WIDTH - mul [bp+StartY] ;offset in page of top rectangle scan line - mov di,[bp+StartX] - shr di,1 - shr di,1 ;X/4 = offset of first rectangle pixel in scan - ; line - add di,ax ;offset of first rectangle pixel in page - 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 - ; 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 - mov al,01h - 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 - jle FillDone ;skip if 0 or negative height - mov si,[bp+EndX] - sub si,[bp+StartX] ;CX = width of rectangle - jle FillDone ;skip if 0 or negative width - FillRowsLoop: - push ax ;remember the plane mask for the left edge - push di ;remember the start offset of the scan line - mov cx,si ;set count of pixels in this scan line - 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 - and al,01111b ; bit, modulo 4 - jnz AddressSet ;advance address if we turned over from - inc di ; plane 3 to plane 0 - mov al,00001b ;set plane mask bit for plane 0 - AddressSet: - loop FillScanLineLoop - pop di ;retrieve the start offset of the scan line - add di,SCREEN_WIDTH ;point to the start of the next scan - ; line of the rectangle - pop ax ;retrieve the plane mask for the left edge - dec bx ;count down scan lines - jnz FillRowsLoop - FillDone: - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - _FillRectangleX endp - end + mov ax,SCREEN_WIDTH + mul [bp+StartY] ;offset in page of top rectangle scan line + mov di,[bp+StartX] + shr di,1 + shr di,1 ;X/4 = offset of first rectangle pixel in scan + ; line + add di,ax ;offset of first rectangle pixel in page + 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 + ; 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 + mov al,01h + 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 + jle FillDone ;skip if 0 or negative height + mov si,[bp+EndX] + sub si,[bp+StartX] ;CX = width of rectangle + jle FillDone ;skip if 0 or negative width +FillRowsLoop: + push ax ;remember the plane mask for the left edge + push di ;remember the start offset of the scan line + mov cx,si ;set count of pixels in this scan line +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 + and al,01111b ; bit, modulo 4 + jnz AddressSet ;advance address if we turned over from + inc di ; plane 3 to plane 0 + mov al,00001b ;set plane mask bit for plane 0 +AddressSet: + loop FillScanLineLoop + pop di ;retrieve the start offset of the scan line + add di,SCREEN_WIDTH ;point to the start of the next scan + ; line of the rectangle + pop ax ;retrieve the plane mask for the left edge + dec bx ;count down scan lines + jnz FillRowsLoop +FillDone: + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame + ret +_FillRectangleX endp + end +``` 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 diff --git a/47-05.md b/47-05.md index da03479..391db2f 100644 --- a/47-05.md +++ b/47-05.md @@ -22,130 +22,132 @@ pages: 887-889 **LISTING 47.5 L47-5.ASM** - ; Mode X (320x240, 256 colors) rectangle fill routine. Works on all - ; VGAs. Uses medium-speed approach that selects each plane only once - ; per rectangle; this results in a fade-in effect for large - ; rectangles. Fills up to but not including the column at EndX and the - ; row at EndY. No clipping is performed. - ; C near-callable as: - ; - ; void FillRectangleX(int StartX, int StartY, int EndX, int EndY, - ; unsigned int PageBase, int Color); +```nasm +; Mode X (320x240, 256 colors) rectangle fill routine. Works on all +; VGAs. Uses medium-speed approach that selects each plane only once +; per rectangle; this results in a fade-in effect for large +; rectangles. Fills up to but not including the column at EndX and the +; row at EndY. No clipping is performed. +; C near-callable as: +; +; void FillRectangleX(int StartX, int StartY, int EndX, int EndY, +; unsigned int PageBase, int Color); - SC_INDEX equ 03c4h ;Sequence Controller Index - MAP_MASK equ 02h ;index in SC of Map Mask register - SCREEN_SEG equ 0a000h ;segment of display memory in mode X - SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line - ; to the next - parms struc - dw 2 dup (?) ;pushed BP and return address - StartX dw ? ;X coordinate of upper left corner of rect - StartY dw ? ;Y coordinate of upper left corner of rect - EndX dw ? ;X coordinate of lower right corner of rect - ; (the row at EndX is not filled) - EndY dw ? ;Y coordinate of lower right corner of rect - ; (the column at EndY is not filled) - PageBase dw ? ;base offset in display memory of page in - ; which to fill rectangle - Color dw ? ;color in which to draw pixel - parms ends +SC_INDEX equ 03c4h ;Sequence Controller Index +MAP_MASK equ 02h ;index in SC of Map Mask register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line + ; to the next +parms struc + dw 2 dup (?) ;pushed BP and return address +StartX dw ? ;X coordinate of upper left corner of rect +StartY dw ? ;Y coordinate of upper left corner of rect +EndX dw ? ;X coordinate of lower right corner of rect + ; (the row at EndX is not filled) +EndY dw ? ;Y coordinate of lower right corner of rect + ; (the column at EndY is not filled) +PageBase dw ? ;base offset in display memory of page in + ; which to fill rectangle +Color dw ? ;color in which to draw pixel +parms ends - StartOffset equ -2 ;local storage for start offset of rectangle - Width equ -4 ;local storage for address width of rectangle - Height equ -6 ;local storage for height of rectangle - PlaneInfo equ -8 ;local storage for plane # and plane mask - STACK_FRAME_SIZE equ 8 +StartOffset equ -2 ;local storage for start offset of rectangle +Width equ -4 ;local storage for address width of rectangle +Height equ -6 ;local storage for height of rectangle +PlaneInfo equ -8 ;local storage for plane # and plane mask +STACK_FRAME_SIZE equ 8 - .model small - .code - public _FillRectangleX - _FillRectangleX proc near - 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 di + .model small + .code + public _FillRectangleX +_FillRectangleX proc near + 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 di - cld - mov ax,SCREEN_WIDTH - mul [bp+StartY] ;offset in page of top rectangle scan line - mov di,[bp+StartX] - shr di,1 - shr di,1 ;X/4 = offset of first rectangle pixel in scan - ; line - add di,ax ;offset of first rectangle pixel in page - 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 [bp+StartOffset],di ; address - mov dx,SC_INDEX ;set the Sequence Controller Index to - mov al,MAP_MASK ; point to the Map Mask register - out dx,al - mov bx,[bp+EndY] - sub bx,[bp+StartY] ;BX = height of rectangle - jle FillDone ;skip if 0 or negative height - mov [bp+Height],bx - mov dx,[bp+EndX] - mov cx,[bp+StartX] - cmp dx,cx - jle FillDone ;skip if 0 or negative width - dec dx - and cx,not 011b - sub dx,cx - shr dx,1 - shr dx,1 - inc dx ;# of addresses across rectangle to fill - mov [bp+Width],dx - mov word ptr [bp+PlaneInfo],0001h - ;lower byte = plane mask for plane 0, - ; upper byte = plane # for plane 0 - FillPlanesLoop: - mov ax,word ptr [bp+PlaneInfo] - mov dx,SC_INDEX+1 ;point DX to the SC Data register - out dx,al ;set the plane for this pixel - mov di,[bp+StartOffset] ;point ES:DI to rectangle start - mov dx,[bp+Width] - mov cl,byte ptr [bp+StartX] - and cl,011b ;plane # of first pixel in initial byte - cmp ah,cl ;do we draw this plane in the initial byte? - jae InitAddrSet ;yes - dec dx ;no, so skip the initial byte - jz FillLoopBottom ;skip this plane if no pixels in it - inc di - InitAddrSet: - mov cl,byte ptr [bp+EndX] - dec cl - and cl,011b ;plane # of last pixel in final byte - cmp ah,cl ;do we draw this plane in the final byte? - jbe WidthSet ;yes - dec dx ;no, so skip the final byte - jz FillLoopBottom ;skip this planes if no pixels in it - WidthSet: - mov si,SCREEN_WIDTH - sub si,dx ;distance from end of one scan line to start - ; of next - mov bx,[bp+Height] ;# of lines to fill - mov al,byte ptr [bp+Color] ;color with which to fill - FillRowsLoop: - mov cx,dx ;# of bytes across scan line - rep stosb ;fill the scan line in this plane - add di,si ;point to the start of the next scan - ; line of the rectangle - dec bx ;count down scan lines - jnz FillRowsLoop - FillLoopBottom: - mov ax,word ptr [bp+PlaneInfo] - shl al,1 ;set the plane bit to the next plane - inc ah ;increment the plane # - mov word ptr [bp+PlaneInfo],ax - cmp ah,4 ;have we done all planes? - jnz FillPlanesLoop ;continue if any more planes - FillDone: - pop di ;restore caller's register variables - pop si - mov sp,bp ;discard storage for local variables - pop bp ;restore caller's stack frame - ret - _FillRectangleX endp - end + cld + mov ax,SCREEN_WIDTH + mul [bp+StartY] ;offset in page of top rectangle scan line + mov di,[bp+StartX] + shr di,1 + shr di,1 ;X/4 = offset of first rectangle pixel in scan + ; line + add di,ax ;offset of first rectangle pixel in page + 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 [bp+StartOffset],di ; address + mov dx,SC_INDEX ;set the Sequence Controller Index to + mov al,MAP_MASK ; point to the Map Mask register + out dx,al + mov bx,[bp+EndY] + sub bx,[bp+StartY] ;BX = height of rectangle + jle FillDone ;skip if 0 or negative height + mov [bp+Height],bx + mov dx,[bp+EndX] + mov cx,[bp+StartX] + cmp dx,cx + jle FillDone ;skip if 0 or negative width + dec dx + and cx,not 011b + sub dx,cx + shr dx,1 + shr dx,1 + inc dx ;# of addresses across rectangle to fill + mov [bp+Width],dx + mov word ptr [bp+PlaneInfo],0001h + ;lower byte = plane mask for plane 0, + ; upper byte = plane # for plane 0 +FillPlanesLoop: + mov ax,word ptr [bp+PlaneInfo] + mov dx,SC_INDEX+1 ;point DX to the SC Data register + out dx,al ;set the plane for this pixel + mov di,[bp+StartOffset] ;point ES:DI to rectangle start + mov dx,[bp+Width] + mov cl,byte ptr [bp+StartX] + and cl,011b ;plane # of first pixel in initial byte + cmp ah,cl ;do we draw this plane in the initial byte? + jae InitAddrSet ;yes + dec dx ;no, so skip the initial byte + jz FillLoopBottom ;skip this plane if no pixels in it + inc di +InitAddrSet: + mov cl,byte ptr [bp+EndX] + dec cl + and cl,011b ;plane # of last pixel in final byte + cmp ah,cl ;do we draw this plane in the final byte? + jbe WidthSet ;yes + dec dx ;no, so skip the final byte + jz FillLoopBottom ;skip this planes if no pixels in it +WidthSet: + mov si,SCREEN_WIDTH + sub si,dx ;distance from end of one scan line to start + ; of next + mov bx,[bp+Height] ;# of lines to fill + mov al,byte ptr [bp+Color] ;color with which to fill +FillRowsLoop: + mov cx,dx ;# of bytes across scan line + rep stosb ;fill the scan line in this plane + add di,si ;point to the start of the next scan + ; line of the rectangle + dec bx ;count down scan lines + jnz FillRowsLoop +FillLoopBottom: + mov ax,word ptr [bp+PlaneInfo] + shl al,1 ;set the plane bit to the next plane + inc ah ;increment the plane # + mov word ptr [bp+PlaneInfo],ax + cmp ah,4 ;have we done all planes? + jnz FillPlanesLoop ;continue if any more planes +FillDone: + pop di ;restore caller's register variables + pop si + mov sp,bp ;discard storage for local variables + pop bp ;restore caller's stack frame + ret +_FillRectangleX endp + end +``` diff --git a/47-07.md b/47-07.md index fc8e1d0..be6f0bd 100644 --- a/47-07.md +++ b/47-07.md @@ -12,119 +12,121 @@ pages: 891-893 **LISTING 47.6 L47-6.ASM** - ; Mode X (320x240, 256 colors) rectangle fill routine. Works on all - ; VGAs. Uses fast approach that fans data out to up to four planes at - ; once to draw up to four pixels at once. Fills up to but not - ; including the column at EndX and the row at EndY. No clipping is - ; performed. - ; C near-callable as: - ; void FillRectangleX(int StartX, int StartY, int EndX, int EndY, - ; unsigned int PageBase, int Color); +```nasm +; Mode X (320x240, 256 colors) rectangle fill routine. Works on all +; VGAs. Uses fast approach that fans data out to up to four planes at +; once to draw up to four pixels at once. Fills up to but not +; including the column at EndX and the row at EndY. No clipping is +; performed. +; C near-callable as: +; void FillRectangleX(int StartX, int StartY, int EndX, int EndY, +; unsigned int PageBase, int Color); - SC_INDEX equ 03c4h ;Sequence Controller Index - MAP_MASK equ 02h ;index in SC of Map Mask register - SCREEN_SEG equ 0a000h ;segment of display memory in mode X - SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line - ; to the next - parms struc - dw 2 dup (?) ;pushed BP and return address - StartX dw ? ;X coordinate of upper left corner of rect - StartY dw ? ;Y coordinate of upper left corner of rect - EndX dw ? ;X coordinate of lower right corner of rect - ; (the row at EndX is not filled) - EndY dw ? ;Y coordinate of lower right corner of rect - ; (the column at EndY is not filled) - PageBase dw ? ;base offset in display memory of page in - ; which to fill rectangle - Color dw ? ;color in which to draw pixel - parms ends +SC_INDEX equ 03c4h ;Sequence Controller Index +MAP_MASK equ 02h ;index in SC of Map Mask register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line + ; to the next +parms struc + dw 2 dup (?) ;pushed BP and return address +StartX dw ? ;X coordinate of upper left corner of rect +StartY dw ? ;Y coordinate of upper left corner of rect +EndX dw ? ;X coordinate of lower right corner of rect + ; (the row at EndX is not filled) +EndY dw ? ;Y coordinate of lower right corner of rect + ; (the column at EndY is not filled) +PageBase dw ? ;base offset in display memory of page in + ; which to fill rectangle +Color dw ? ;color in which to draw pixel +parms ends - .model small - .data - ; Plane masks for clipping left and right edges of rectangle. - LeftClipPlaneMask db 00fh,00eh,00ch,008h - RightClipPlaneMask db 00fh,001h,003h,007h - .code - public _FillRectangleX - _FillRectangleX proc near - push bp ;preserve caller's stack frame - mov bp,sp ;point to local stack frame - push si ;preserve caller's register variables - push di + .model small + .data +; Plane masks for clipping left and right edges of rectangle. +LeftClipPlaneMask db 00fh,00eh,00ch,008h +RightClipPlaneMask db 00fh,001h,003h,007h + .code + public _FillRectangleX +_FillRectangleX proc near + push bp ;preserve caller's stack frame + mov bp,sp ;point to local stack frame + push si ;preserve caller's register variables + push di - cld - mov ax,SCREEN_WIDTH - mul [bp+StartY] ;offset in page of top rectangle scan line - mov di,[bp+StartX] - shr di,1 ;X/4 = offset of first rectangle pixel in scan - shr di,1 ; line - add di,ax ;offset of first rectangle pixel in page - 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 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 si,[bp+StartX] - and si,0003h ;look up left edge plane mask - mov bh,LeftClipPlaneMask[si]; to clip & put in BH - mov si,[bp+EndX] - and si,0003h ;look up right edge plane - mov bl,RightClipPlaneMask[si]; mask to clip & put in BL + cld + mov ax,SCREEN_WIDTH + mul [bp+StartY] ;offset in page of top rectangle scan line + mov di,[bp+StartX] + shr di,1 ;X/4 = offset of first rectangle pixel in scan + shr di,1 ; line + add di,ax ;offset of first rectangle pixel in page + 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 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 si,[bp+StartX] + and si,0003h ;look up left edge plane mask + mov bh,LeftClipPlaneMask[si]; to clip & put in BH + mov si,[bp+EndX] + and si,0003h ;look up right edge plane + mov bl,RightClipPlaneMask[si]; mask to clip & put in BL - mov cx,[bp+EndX] ;calculate # of addresses across rect - mov si,[bp+StartX] - cmp cx,si - jle FillDone ;skip if 0 or negative width - dec cx - and si,not 011b - 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- - ; 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 - sub bp,cx ;distance from end of one scan line to start - dec bp ; of next - FillRowsLoop: - push cx ;remember width in addresses - 1 - mov al,bh ;put left-edge clip mask in AL - out dx,al ;set the left-edge plane (clip) mask - 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 - 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 - mov al,ah ;put color in AL - rep stosb ;draw the middle addresses four pixels apiece - DoRightEdge: - mov al,bl ;put right-edge clip mask in AL - out dx,al ;set the right-edge plane (clip) mask - mov al,ah ;put color in AL - stosb ;draw the right edge - FillLoopBottom: - add di,bp ;point to the start of the next scan line of - ; the rectangle - pop cx ;retrieve width in addresses - 1 - dec si ;count down scan lines - jnz FillRowsLoop - FillDone: - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - _FillRectangleX endp - end + mov cx,[bp+EndX] ;calculate # of addresses across rect + mov si,[bp+StartX] + cmp cx,si + jle FillDone ;skip if 0 or negative width + dec cx + and si,not 011b + 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- + ; 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 + sub bp,cx ;distance from end of one scan line to start + dec bp ; of next +FillRowsLoop: + push cx ;remember width in addresses - 1 + mov al,bh ;put left-edge clip mask in AL + out dx,al ;set the left-edge plane (clip) mask + 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 + 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 + mov al,ah ;put color in AL + rep stosb ;draw the middle addresses four pixels apiece +DoRightEdge: + mov al,bl ;put right-edge clip mask in AL + out dx,al ;set the right-edge plane (clip) mask + mov al,ah ;put color in AL + stosb ;draw the right edge +FillLoopBottom: + add di,bp ;point to the start of the next scan line of + ; the rectangle + pop cx ;retrieve width in addresses - 1 + dec si ;count down scan lines + jnz FillRowsLoop +FillDone: + pop di ;restore caller's register variables + pop si + 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 @@ -137,27 +139,29 @@ copies, blits, and pattern fills. **LISTING 47.7 L47-7.C** - /* Program to demonstrate mode X (320x240, 256-colors) rectangle - fill by drawing adjacent 20x20 rectangles in successive colors from - 0 on up across and down the screen */ - #include - #include +```c +/* Program to demonstrate mode X (320x240, 256-colors) rectangle + fill by drawing adjacent 20x20 rectangles in successive colors from + 0 on up across and down the screen */ +#include +#include - void Set320x240Mode(void); - void FillRectangleX(int, int, int, int, unsigned int, int); +void Set320x240Mode(void); +void FillRectangleX(int, int, int, int, unsigned int, int); - void main() { - int i,j; - union REGS regset; +void main() { + int i,j; + union REGS regset; - Set320x240Mode(); - FillRectangleX(0,0,320,240,0,0); /* clear the screen to black */ - for (j = 1; j < 220; j += 21) { - for (i = 1; i < 300; i += 21) { - FillRectangleX(i, j, i+20, j+20, 0, ((j/21*15)+i/21) & 0xFF); - } - } - getch(); - regset.x.ax = 0x0003; /* switch back to text mode and done */ - int86(0x10, ®set, ®set); - } + Set320x240Mode(); + FillRectangleX(0,0,320,240,0,0); /* clear the screen to black */ + for (j = 1; j < 220; j += 21) { + for (i = 1; i < 300; i += 21) { + FillRectangleX(i, j, i+20, j+20, 0, ((j/21*15)+i/21) & 0xFF); + } + } + getch(); + regset.x.ax = 0x0003; /* switch back to text mode and done */ + int86(0x10, ®set, ®set); +} +``` diff --git a/48-01.md b/48-01.md index e1e0a65..e39f4a3 100644 --- a/48-01.md +++ b/48-01.md @@ -79,47 +79,49 @@ previous chapter's listings.) **LISTING 48.1 L48-1.C** - /* Program to demonstrate Mode X (320x240, 256 colors) patterned - rectangle fills by filling the screen with adjacent 80x60 - rectangles in a variety of patterns. Tested with Borland C++ - in C compilation mode and the small model */ - #include - #include +```c +/* Program to demonstrate Mode X (320x240, 256 colors) patterned + rectangle fills by filling the screen with adjacent 80x60 + rectangles in a variety of patterns. Tested with Borland C++ + in C compilation mode and the small model */ +#include +#include - void Set320x240Mode(void); - void FillPatternX(int, int, int, int, unsigned int, char*); +void Set320x240Mode(void); +void FillPatternX(int, int, int, int, unsigned int, char*); - /* 16 4x4 patterns */ - static char Patt0[]={10,0,10,0,0,10,0,10,10,0,10,0,0,10,0,10}; - static char Patt1[]={9,0,0,0,0,9,0,0,0,0,9,0,0,0,0,9}; - static char Patt2[]={5,0,0,0,0,0,5,0,5,0,0,0,0,0,5,0}; - static char Patt3[]={14,0,0,14,0,14,14,0,0,14,14,0,14,0,0,14}; - static char Patt4[]={15,15,15,1,15,15,1,1,15,1,1,1,1,1,1,1}; - static char Patt5[]={12,12,12,12,6,6,6,12,6,6,6,12,6,6,6,12}; - static char Patt6[]={80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,15}; - static char Patt7[]={78,78,78,78,80,80,80,80,82,82,82,82,84,84,84,84}; - static char Patt8[]={78,80,82,84,80,82,84,78,82,84,78,80,84,78,80,82}; - static char Patt9[]={78,80,82,84,78,80,82,84,78,80,82,84,78,80,82,84}; - static char Patt10[]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; - static char Patt11[]={0,1,2,3,0,1,2,3,0,1,2,3,0,1,2,3}; - static char Patt12[]={14,14,9,9,14,9,9,14,9,9,14,14,9,14,14,9}; - static char Patt13[]={15,8,8,8,15,15,15,8,15,15,15,8,15,8,8,8}; - static char Patt14[]={3,3,3,3,3,7,7,3,3,7,7,3,3,3,3,3}; - static char Patt15[]={0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,89}; - /* Table of pointers to the 16 4x4 patterns with which to draw */ - static char* PattTable[] = {Patt0,Patt1,Patt2,Patt3,Patt4,Patt5,Patt6, - Patt7,Patt8,Patt9,Patt10,Patt11,Patt12,Patt13,Patt14,Patt15}; - void main() { - int i,j; - union REGS regset; +/* 16 4x4 patterns */ +static char Patt0[]={10,0,10,0,0,10,0,10,10,0,10,0,0,10,0,10}; +static char Patt1[]={9,0,0,0,0,9,0,0,0,0,9,0,0,0,0,9}; +static char Patt2[]={5,0,0,0,0,0,5,0,5,0,0,0,0,0,5,0}; +static char Patt3[]={14,0,0,14,0,14,14,0,0,14,14,0,14,0,0,14}; +static char Patt4[]={15,15,15,1,15,15,1,1,15,1,1,1,1,1,1,1}; +static char Patt5[]={12,12,12,12,6,6,6,12,6,6,6,12,6,6,6,12}; +static char Patt6[]={80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,15}; +static char Patt7[]={78,78,78,78,80,80,80,80,82,82,82,82,84,84,84,84}; +static char Patt8[]={78,80,82,84,80,82,84,78,82,84,78,80,84,78,80,82}; +static char Patt9[]={78,80,82,84,78,80,82,84,78,80,82,84,78,80,82,84}; +static char Patt10[]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; +static char Patt11[]={0,1,2,3,0,1,2,3,0,1,2,3,0,1,2,3}; +static char Patt12[]={14,14,9,9,14,9,9,14,9,9,14,14,9,14,14,9}; +static char Patt13[]={15,8,8,8,15,15,15,8,15,15,15,8,15,8,8,8}; +static char Patt14[]={3,3,3,3,3,7,7,3,3,7,7,3,3,3,3,3}; +static char Patt15[]={0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,89}; +/* Table of pointers to the 16 4x4 patterns with which to draw */ +static char* PattTable[] = {Patt0,Patt1,Patt2,Patt3,Patt4,Patt5,Patt6, + Patt7,Patt8,Patt9,Patt10,Patt11,Patt12,Patt13,Patt14,Patt15}; +void main() { + int i,j; + union REGS regset; - Set320x240Mode(); - for (j = 0; j < 4; j++) { - for (i = 0; i < 4; i++) { - FillPatternX(i*80,j*60,i*80+80,j*60+60,0,PattTable[j*4+i]); - } - } - getch(); - regset.x.ax = 0x0003; /* switch back to text mode and done */ - int86(0x10, ®set, ®set); - } + Set320x240Mode(); + for (j = 0; j < 4; j++) { + for (i = 0; i < 4; i++) { + FillPatternX(i*80,j*60,i*80+80,j*60+60,0,PattTable[j*4+i]); + } + } + getch(); + regset.x.ax = 0x0003; /* switch back to text mode and done */ + int86(0x10, ®set, ®set); +} +``` diff --git a/48-02.md b/48-02.md index a7dda33..76dae08 100644 --- a/48-02.md +++ b/48-02.md @@ -12,178 +12,180 @@ pages: 900-903 **LISTING 48.2 L48-2.ASM** - ; Mode X (320x240, 256 colors) rectangle 4x4 pattern fill routine. - ; Upper-left corner of pattern is always aligned to a multiple-of-4 - ; row and column. Works on all VGAs. Uses approach of copying the - ; pattern to off-screen display memory, then loading the latches with - ; the pattern for each scan line and filling each scan line four - ; pixels at a time. Fills up to but not including the column at EndX - ; and the row at EndY. No clipping is performed. All ASM code tested - ; with TASM. C near-callable as: - ; - ; void FillPatternX(int StartX, int StartY, int EndX, int EndY, - ; unsigned int PageBase, char* Pattern); +```nasm +; Mode X (320x240, 256 colors) rectangle 4x4 pattern fill routine. +; Upper-left corner of pattern is always aligned to a multiple-of-4 +; row and column. Works on all VGAs. Uses approach of copying the +; pattern to off-screen display memory, then loading the latches with +; the pattern for each scan line and filling each scan line four +; pixels at a time. Fills up to but not including the column at EndX +; and the row at EndY. No clipping is performed. All ASM code tested +; with TASM. C near-callable as: +; +; void FillPatternX(int StartX, int StartY, int EndX, int EndY, +; unsigned int PageBase, char* Pattern); - SC_INDEX equ 03c4h ;Sequence Controller Index register port - MAP_MASK equ 02h ;index in SC of Map Mask register - GC_INDEX equ 03ceh ;Graphics Controller Index register port - BIT_MASK equ 08h ;index in GC of Bit Mask register - PATTERN_BUFFER equ 0fffch ;offset in screen memory of the buffer used - ; to store each pattern during drawing - SCREEN_SEG equ 0a000h ;segment of display memory in Mode X - SCREEN_WIDTH equ 80 ;width of screen in addresses from one scan - ; line to the next - parms struc - dw 2 dup (?) ;pushed BP and return address - StartX dw ? ;X coordinate of upper left corner of rect - StartY dw ? ;Y coordinate of upper left corner of rect - EndX dw ? ;X coordinate of lower right corner of rect - ; (the row at EndX is not filled) - EndY dw ? ;Y coordinate of lower right corner of rect - ; (the column at EndY is not filled) +SC_INDEX equ 03c4h ;Sequence Controller Index register port +MAP_MASK equ 02h ;index in SC of Map Mask register +GC_INDEX equ 03ceh ;Graphics Controller Index register port +BIT_MASK equ 08h ;index in GC of Bit Mask register +PATTERN_BUFFER equ 0fffch ;offset in screen memory of the buffer used + ; to store each pattern during drawing +SCREEN_SEG equ 0a000h ;segment of display memory in Mode X +SCREEN_WIDTH equ 80 ;width of screen in addresses from one scan + ; line to the next +parms struc + dw 2 dup (?) ;pushed BP and return address +StartX dw ? ;X coordinate of upper left corner of rect +StartY dw ? ;Y coordinate of upper left corner of rect +EndX dw ? ;X coordinate of lower right corner of rect + ; (the row at EndX is not filled) +EndY dw ? ;Y coordinate of lower right corner of rect + ; (the column at EndY is not filled) - PageBase dw ? ;base offset in display memory of page in - ; which to fill rectangle - Pattern dw ? ;4x4 pattern with which to fill rectangle - parms ends +PageBase dw ? ;base offset in display memory of page in + ; which to fill rectangle +Pattern dw ? ;4x4 pattern with which to fill rectangle +parms ends - NextScanOffset equ -2 ;local storage for distance from end of one - ; scan line to start of next - RectAddrWidth equ -4 ;local storage for address width of rectangle - Height equ -6 ;local storage for height of rectangle - STACK_FRAME_SIZE equ 6 +NextScanOffset equ -2 ;local storage for distance from end of one + ; scan line to start of next +RectAddrWidth equ -4 ;local storage for address width of rectangle +Height equ -6 ;local storage for height of rectangle +STACK_FRAME_SIZE equ 6 - .model small - .data - ; Plane masks for clipping left and right edges of rectangle. - LeftClipPlaneMask db 00fh,00eh,00ch,008h - RightClipPlaneMask db 00fh,001h,003h,007h - .code - public _FillPatternX - _FillPatternX proc near - 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 di + .model small + .data +; Plane masks for clipping left and right edges of rectangle. +LeftClipPlaneMask db 00fh,00eh,00ch,008h +RightClipPlaneMask db 00fh,001h,003h,007h + .code + public _FillPatternX +_FillPatternX proc near + 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 di - cld - mov ax,SCREEN_SEG ;point ES to display memory - mov es,ax - ;copy pattern to display memory buffer - mov si,[bp+Pattern] ;point to pattern to fill with - mov di,PATTERN_BUFFER ;point ES:DI to pattern buffer - mov dx,SC_INDEX ;point Sequence Controller Index to - mov al,MAP_MASK ; Map Mask - out dx,al - inc dx ;point to SC Data register - mov cx,4 ;4 pixel quadruplets in pattern - DownloadPatternLoop: - mov al,1 ; - out dx,al ;select plane 0 for writes - movsb ;copy over next plane 0 pattern pixel - dec di ;stay at same address for next plane - mov al,2 ; - out dx,al ;select plane 1 for writes - movsb ;copy over next plane 1 pattern pixel - dec di ;stay at same address for next plane - mov al,4 ; - out dx,al ;select plane 2 for writes - movsb ;copy over next plane 2 pattern pixel - dec di ;stay at same address for next plane - mov al,8 ; - out dx,al ;select plane 3 for writes - movsb ;copy over next plane 3 pattern pixel - ; and advance address - loop DownloadPatternLoop + cld + mov ax,SCREEN_SEG ;point ES to display memory + mov es,ax + ;copy pattern to display memory buffer + mov si,[bp+Pattern] ;point to pattern to fill with + mov di,PATTERN_BUFFER ;point ES:DI to pattern buffer + mov dx,SC_INDEX ;point Sequence Controller Index to + mov al,MAP_MASK ; Map Mask + out dx,al + inc dx ;point to SC Data register + mov cx,4 ;4 pixel quadruplets in pattern +DownloadPatternLoop: + mov al,1 ; + out dx,al ;select plane 0 for writes + movsb ;copy over next plane 0 pattern pixel + dec di ;stay at same address for next plane + mov al,2 ; + out dx,al ;select plane 1 for writes + movsb ;copy over next plane 1 pattern pixel + dec di ;stay at same address for next plane + mov al,4 ; + out dx,al ;select plane 2 for writes + movsb ;copy over next plane 2 pattern pixel + dec di ;stay at same address for next plane + mov al,8 ; + out dx,al ;select plane 3 for writes + movsb ;copy over next plane 3 pattern pixel + ; and advance address + loop DownloadPatternLoop - mov dx,GC_INDEX ;set the bit mask to select all bits - mov ax,00000h+BIT_MASK ; from the latches and none from - out dx,ax ; the CPU, so that we can write the - ; latch contents directly to memory - mov ax,[bp+StartY] ;top rectangle scan line - mov si,ax - and si,011b ;top rect scan line modulo 4 - add si,PATTERN_BUFFER ;point to pattern scan line that - ; maps to top line of rect to draw - mov dx,SCREEN_WIDTH - mul dx ;offset in page of top rectangle scan line - mov di,[bp+StartX] - mov bx,di - shr di,1 ;X/4 = offset of first rectangle pixel in scan - shr di,1 ; line - add di,ax ;offset of first rectangle pixel in page - add di,[bp+PageBase] ;offset of first rectangle pixel in - ; display memory - and bx,0003h ;look up left edge plane mask - mov ah,LeftClipPlaneMask[bx] ; to clip - mov bx,[bp+EndX] - and bx,0003h ;look up right edge plane - mov al,RightClipPlaneMask[bx] ; mask to clip - mov bx,ax ;put the masks in BX - - mov cx,[bp+EndX] ;calculate # of addresses across rect - mov ax,[bp+StartX] - cmp cx,ax - jle FillDone ;skip if 0 or negative width - dec cx - and ax,not 011b - 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- - ; and right-edge clip masks - MasksSet: - mov ax,[bp+EndY] - sub ax,[bp+StartY] ;AX = height of rectangle - jle FillDone ;skip if 0 or negative height - mov [bp+Height],ax - mov ax,SCREEN_WIDTH - sub ax,cx ;distance from end of one scan line to start - dec ax ; of next - mov [bp+NextScanOffset],ax - mov [bp+RectAddrWidth],cx ;remember width in addresses - 1 - mov dx,SC_INDEX+1 ;point to Sequence Controller Data reg - ; (SC Index still points to Map Mask) - FillRowsLoop: - mov cx,[bp+RectAddrWidth] ;width across - 1 - mov al,es:[si] ;read display memory to latch this scan - ; 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 - 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) - dec cx ;count off left edge 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) - 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) - FillLoopBottom: - add di,[bp+NextScanOffset] ;point to the start of the next scan - ; line of the rectangle - dec word ptr [bp+Height] ;count down scan lines - jnz FillRowsLoop - FillDone: - mov dx,GC_INDEX+1 ;restore the bit mask to its default, - 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 si - mov sp,bp ;discard storage for local variables - pop bp ;restore caller's stack frame - ret - _FillPatternX endp - end + mov dx,GC_INDEX ;set the bit mask to select all bits + mov ax,00000h+BIT_MASK ; from the latches and none from + out dx,ax ; the CPU, so that we can write the + ; latch contents directly to memory + mov ax,[bp+StartY] ;top rectangle scan line + mov si,ax + and si,011b ;top rect scan line modulo 4 + add si,PATTERN_BUFFER ;point to pattern scan line that + ; maps to top line of rect to draw + mov dx,SCREEN_WIDTH + mul dx ;offset in page of top rectangle scan line + mov di,[bp+StartX] + mov bx,di + shr di,1 ;X/4 = offset of first rectangle pixel in scan + shr di,1 ; line + add di,ax ;offset of first rectangle pixel in page + add di,[bp+PageBase] ;offset of first rectangle pixel in + ; display memory + and bx,0003h ;look up left edge plane mask + mov ah,LeftClipPlaneMask[bx] ; to clip + mov bx,[bp+EndX] + and bx,0003h ;look up right edge plane + mov al,RightClipPlaneMask[bx] ; mask to clip + mov bx,ax ;put the masks in BX + + mov cx,[bp+EndX] ;calculate # of addresses across rect + mov ax,[bp+StartX] + cmp cx,ax + jle FillDone ;skip if 0 or negative width + dec cx + and ax,not 011b + 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- + ; and right-edge clip masks +MasksSet: + mov ax,[bp+EndY] + sub ax,[bp+StartY] ;AX = height of rectangle + jle FillDone ;skip if 0 or negative height + mov [bp+Height],ax + mov ax,SCREEN_WIDTH + sub ax,cx ;distance from end of one scan line to start + dec ax ; of next + mov [bp+NextScanOffset],ax + mov [bp+RectAddrWidth],cx ;remember width in addresses - 1 + mov dx,SC_INDEX+1 ;point to Sequence Controller Data reg + ; (SC Index still points to Map Mask) +FillRowsLoop: + mov cx,[bp+RectAddrWidth] ;width across - 1 + mov al,es:[si] ;read display memory to latch this scan + ; 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 +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) + dec cx ;count off left edge 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) +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) +FillLoopBottom: + add di,[bp+NextScanOffset] ;point to the start of the next scan + ; line of the rectangle + dec word ptr [bp+Height] ;count down scan lines + jnz FillRowsLoop +FillDone: + mov dx,GC_INDEX+1 ;restore the bit mask to its default, + 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 si + mov sp,bp ;discard storage for local variables + pop bp ;restore caller's stack frame + ret +_FillPatternX endp + end +``` diff --git a/48-04.md b/48-04.md index f9ca4f4..c650533 100644 --- a/48-04.md +++ b/48-04.md @@ -12,176 +12,178 @@ pages: 905-908 **LISTING 48.3 L48-3.ASM** - ; Mode X (320x240, 256 colors) display memory to display memory copy - ; routine. Left edge of source rectangle modulo 4 must equal left edge - ; of destination rectangle modulo 4. Works on all VGAs. Uses approach - ; of reading 4 pixels at a time from the source into the latches, then - ; writing the latches to the destination. Copies up to but not - ; including the column at SourceEndX and the row at SourceEndY. No - ; clipping is performed. Results are not guaranteed if the source and - ; destination overlap. C near-callable as: - ; - ; void CopyScreenToScreenX(int SourceStartX, int SourceStartY, - ; int SourceEndX, int SourceEndY, int DestStartX, - ; int DestStartY, unsigned int SourcePageBase, - ; unsigned int DestPageBase, int SourceBitmapWidth, - ; int DestBitmapWidth); +```nasm +; Mode X (320x240, 256 colors) display memory to display memory copy +; routine. Left edge of source rectangle modulo 4 must equal left edge +; of destination rectangle modulo 4. Works on all VGAs. Uses approach +; of reading 4 pixels at a time from the source into the latches, then +; writing the latches to the destination. Copies up to but not +; including the column at SourceEndX and the row at SourceEndY. No +; clipping is performed. Results are not guaranteed if the source and +; destination overlap. C near-callable as: +; +; void CopyScreenToScreenX(int SourceStartX, int SourceStartY, +; int SourceEndX, int SourceEndY, int DestStartX, +; int DestStartY, unsigned int SourcePageBase, +; unsigned int DestPageBase, int SourceBitmapWidth, +; int DestBitmapWidth); - SC_INDEX equ 03c4h ;Sequence Controller Index register port - MAP_MASK equ 02h ;index in SC of Map Mask register - GC_INDEX equ 03ceh ;Graphics Controller Index register port - BIT_MASK equ 08h ;index in GC of Bit Mask register - SCREEN_SEG equ 0a000h ;segment of display memory in Mode X +SC_INDEX equ 03c4h ;Sequence Controller Index register port +MAP_MASK equ 02h ;index in SC of Map Mask register +GC_INDEX equ 03ceh ;Graphics Controller Index register port +BIT_MASK equ 08h ;index in GC of Bit Mask register +SCREEN_SEG equ 0a000h ;segment of display memory in Mode X - parms struc - dw 2 dup (?) ;pushed BP and return address - SourceStartX dw ? ;X coordinate of upper-left corner of source - SourceStartY dw ? ;Y coordinate of upper-left corner of source - SourceEndX dw ? ;X coordinate of lower-right corner of source - ; (the row at SourceEndX is not copied) - SourceEndY dw ? ;Y coordinate of lower-right corner of source - ; (the column at SourceEndY is not copied) - DestStartX dw ? ;X coordinate of upper-left corner of dest - DestStartY dw ? ;Y coordinate of upper-left corner of dest - SourcePageBase dw ? ;base offset in display memory of page in - ; which source resides - DestPageBase dw ? ;base offset in display memory of page in - ; which dest resides - SourceBitmapWidth dw ? ;# of pixels across source bitmap - ; (must be a multiple of 4) - DestBitmapWidth dw ? ;# of pixels across dest bitmap - ; (must be a multiple of 4) - parms ends +parms struc + dw 2 dup (?) ;pushed BP and return address +SourceStartX dw ? ;X coordinate of upper-left corner of source +SourceStartY dw ? ;Y coordinate of upper-left corner of source +SourceEndX dw ? ;X coordinate of lower-right corner of source + ; (the row at SourceEndX is not copied) +SourceEndY dw ? ;Y coordinate of lower-right corner of source + ; (the column at SourceEndY is not copied) +DestStartX dw ? ;X coordinate of upper-left corner of dest +DestStartY dw ? ;Y coordinate of upper-left corner of dest +SourcePageBase dw ? ;base offset in display memory of page in + ; which source resides +DestPageBase dw ? ;base offset in display memory of page in + ; which dest resides +SourceBitmapWidth dw ? ;# of pixels across source bitmap + ; (must be a multiple of 4) +DestBitmapWidth dw ? ;# of pixels across dest bitmap + ; (must be a multiple of 4) +parms ends - SourceNextScanOffset equ -2 ;local storage for distance from end of - ; one source scan line to start of next - DestNextScanOffset equ -4 ;local storage for distance from end of - ; one dest scan line to start of next - RectAddrWidth equ -6 ;local storage for address width of rectangle - Height equ -8;local storage for height of rectangle - STACK_FRAME_SIZE equ 8 +SourceNextScanOffset equ -2 ;local storage for distance from end of + ; one source scan line to start of next +DestNextScanOffset equ -4 ;local storage for distance from end of + ; one dest scan line to start of next +RectAddrWidth equ -6 ;local storage for address width of rectangle +Height equ -8;local storage for height of rectangle +STACK_FRAME_SIZE equ 8 - .model small - .data - ; Plane masks for clipping left and right edges of rectangle. - LeftClipPlaneMask db 00fh,00eh,00ch,008h - RightClipPlaneMask db 00fh,001h,003h,007h - .code - public _CopyScreenToScreenX - _CopyScreenToScreenX proc near - 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 di - push ds + .model small + .data +; Plane masks for clipping left and right edges of rectangle. +LeftClipPlaneMask db 00fh,00eh,00ch,008h +RightClipPlaneMask db 00fh,001h,003h,007h + .code + public _CopyScreenToScreenX +_CopyScreenToScreenX proc near + 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 di + push ds - cld - mov dx,GC_INDEX ;set the bit mask to select all bits - mov ax,00000h+BIT_MASK ; from the latches and none from - out dx,ax ; the CPU, so that we can write the - ; latch contents directly to memory - mov ax,SCREEN_SEG ;point ES to display memory - mov es,ax - mov ax,[bp+DestBitmapWidth] - shr ax,1 ;convert to width in addresses - shr ax,1 - mul [bp+DestStartY] ;top dest rect scan line - mov di,[bp+DestStartX] - shr di,1 ;X/4 = offset of first dest rect pixel in - 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 - mov ax,[bp+SourceBitmapWidth] - shr ax,1 ;convert to width in addresses - shr ax,1 - mul [bp+SourceStartY] ;top source rect scan line - mov si,[bp+SourceStartX] - mov bx,si - shr si,1 ;X/4 = offset of first source rect pixel in - shr si,1 ; scan line - add si,ax ;offset of first source rect pixel in page - add si,[bp+SourcePageBase] ;offset of first source rect - ; pixel in display memory - and bx,0003h ;look up left edge plane mask - mov ah,LeftClipPlaneMask[bx] ; to clip - mov bx,[bp+SourceEndX] - and bx,0003h ;look up right-edge plane - mov al,RightClipPlaneMask[bx] ; mask to clip - mov bx,ax ;put the masks in BX - - mov cx,[bp+SourceEndX] ;calculate # of addresses across - mov ax,[bp+SourceStartX] ; rect - cmp cx,ax - jle CopyDone ;skip if 0 or negative width - dec cx - and ax,not 011b - 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 - ; left- and right-edge clip masks - MasksSet: - mov ax,[bp+SourceEndY] - sub ax,[bp+SourceStartY] ;AX = height of rectangle - jle CopyDone ;skip if 0 or negative height - mov [bp+Height],ax - mov ax,[bp+DestBitmapWidth] - shr ax,1 ;convert to width in addresses - shr ax,1 - sub ax,cx ;distance from end of one dest scan line to - dec ax ; start of next - mov [bp+DestNextScanOffset],ax - mov ax,[bp+SourceBitmapWidth] - shr ax,1 ;convert to width in addresses - shr ax,1 - sub ax,cx ;distance from end of one source scan line to - dec ax ; start of next - mov [bp+SourceNextScanOffset],ax - mov [bp+RectAddrWidth],cx ;remember width in addresses - 1 - ;----------------------BUG FIX - mov dx,SC_INDEX - mov al,MAP_MASK - out dx,al ;point SC Index reg to Map Mask - inc dx ;point to SC Data reg - ;----------------------BUG FIX - mov ax,es ;DS=ES=screen segment for MOVS - mov ds,ax - CopyRowsLoop: - mov cx,[bp+RectAddrWidth] ;width across - 1 - mov al,bh ;put left-edge clip mask in AL - out dx,al ;set the left-edge plane (clip) mask - movsb ;copy the left edge (pixels go through - ; latches) - dec cx ;count off left edge 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 - rep movsb ;draw the middle addresses four pixels apiece - ; (pixels copied through latches) - DoRightEdge: - mov al,bl ;put right-edge clip mask in AL - out dx,al ;set the right-edge plane (clip) mask - movsb ;draw the right edge (pixels copied through - ; latches) - CopyLoopBottom: - add si,[bp+SourceNextScanOffset] ;point to the start of - add di,[bp+DestNextScanOffset] ; next source & dest lines - dec word ptr [bp+Height] ;count down scan lines - jnz CopyRowsLoop - CopyDone: - mov dx,GC_INDEX+1 ;restore the bit mask to its default, - 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 ds - pop di ;restore caller's register variables - pop si - mov sp,bp ;discard storage for local variables - pop bp ;restore caller's stack frame - ret - _CopyScreenToScreenX endp - end + cld + mov dx,GC_INDEX ;set the bit mask to select all bits + mov ax,00000h+BIT_MASK ; from the latches and none from + out dx,ax ; the CPU, so that we can write the + ; latch contents directly to memory + mov ax,SCREEN_SEG ;point ES to display memory + mov es,ax + mov ax,[bp+DestBitmapWidth] + shr ax,1 ;convert to width in addresses + shr ax,1 + mul [bp+DestStartY] ;top dest rect scan line + mov di,[bp+DestStartX] + shr di,1 ;X/4 = offset of first dest rect pixel in + 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 + mov ax,[bp+SourceBitmapWidth] + shr ax,1 ;convert to width in addresses + shr ax,1 + mul [bp+SourceStartY] ;top source rect scan line + mov si,[bp+SourceStartX] + mov bx,si + shr si,1 ;X/4 = offset of first source rect pixel in + shr si,1 ; scan line + add si,ax ;offset of first source rect pixel in page + add si,[bp+SourcePageBase] ;offset of first source rect + ; pixel in display memory + and bx,0003h ;look up left edge plane mask + mov ah,LeftClipPlaneMask[bx] ; to clip + mov bx,[bp+SourceEndX] + and bx,0003h ;look up right-edge plane + mov al,RightClipPlaneMask[bx] ; mask to clip + mov bx,ax ;put the masks in BX + + mov cx,[bp+SourceEndX] ;calculate # of addresses across + mov ax,[bp+SourceStartX] ; rect + cmp cx,ax + jle CopyDone ;skip if 0 or negative width + dec cx + and ax,not 011b + 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 + ; left- and right-edge clip masks +MasksSet: + mov ax,[bp+SourceEndY] + sub ax,[bp+SourceStartY] ;AX = height of rectangle + jle CopyDone ;skip if 0 or negative height + mov [bp+Height],ax + mov ax,[bp+DestBitmapWidth] + shr ax,1 ;convert to width in addresses + shr ax,1 + sub ax,cx ;distance from end of one dest scan line to + dec ax ; start of next + mov [bp+DestNextScanOffset],ax + mov ax,[bp+SourceBitmapWidth] + shr ax,1 ;convert to width in addresses + shr ax,1 + sub ax,cx ;distance from end of one source scan line to + dec ax ; start of next + mov [bp+SourceNextScanOffset],ax + mov [bp+RectAddrWidth],cx ;remember width in addresses - 1 +;----------------------BUG FIX +mov dx,SC_INDEX + mov al,MAP_MASK + out dx,al ;point SC Index reg to Map Mask + inc dx ;point to SC Data reg +;----------------------BUG FIX + mov ax,es ;DS=ES=screen segment for MOVS + mov ds,ax +CopyRowsLoop: + mov cx,[bp+RectAddrWidth] ;width across - 1 + mov al,bh ;put left-edge clip mask in AL + out dx,al ;set the left-edge plane (clip) mask + movsb ;copy the left edge (pixels go through + ; latches) + dec cx ;count off left edge 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 + rep movsb ;draw the middle addresses four pixels apiece + ; (pixels copied through latches) +DoRightEdge: + mov al,bl ;put right-edge clip mask in AL + out dx,al ;set the right-edge plane (clip) mask + movsb ;draw the right edge (pixels copied through + ; latches) +CopyLoopBottom: + add si,[bp+SourceNextScanOffset] ;point to the start of + add di,[bp+DestNextScanOffset] ; next source & dest lines + dec word ptr [bp+Height] ;count down scan lines + jnz CopyRowsLoop +CopyDone: + mov dx,GC_INDEX+1 ;restore the bit mask to its default, + 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 ds + pop di ;restore caller's register variables + pop si + mov sp,bp ;discard storage for local variables + pop bp ;restore caller's stack frame + ret +_CopyScreenToScreenX endp + end +``` diff --git a/48-05.md b/48-05.md index e473267..168bbf2 100644 --- a/48-05.md +++ b/48-05.md @@ -56,124 +56,126 @@ inverse of Listing 48.4. **LISTING 48.4 L48-4.ASM** - ; Mode X (320x240, 256 colors) system memory to display memory copy - ; routine. Uses approach of changing the plane for each pixel copied; - ; this is slower than copying all pixels in one plane, then all pixels - ; in the next plane, and so on, but it is simpler; besides, images for - ; which performance is critical should be stored in off-screen memory - ; and copied to the screen via the latches. Copies up to but not - ; including the column at SourceEndX and the row at SourceEndY. No - ; clipping is performed. C near-callable as: - ; - ; void CopySystemToScreenX(int SourceStartX, int SourceStartY, - ; int SourceEndX, int SourceEndY, int DestStartX, - ; int DestStartY, char* SourcePtr, unsigned int DestPageBase, - ; int SourceBitmapWidth, int DestBitmapWidth); +```nasm +; Mode X (320x240, 256 colors) system memory to display memory copy +; routine. Uses approach of changing the plane for each pixel copied; +; this is slower than copying all pixels in one plane, then all pixels +; in the next plane, and so on, but it is simpler; besides, images for +; which performance is critical should be stored in off-screen memory +; and copied to the screen via the latches. Copies up to but not +; including the column at SourceEndX and the row at SourceEndY. No +; clipping is performed. C near-callable as: +; +; void CopySystemToScreenX(int SourceStartX, int SourceStartY, +; int SourceEndX, int SourceEndY, int DestStartX, +; int DestStartY, char* SourcePtr, unsigned int DestPageBase, +; int SourceBitmapWidth, int DestBitmapWidth); - SC_INDEX equ 03c4h ;Sequence Controller Index register port - MAP_MASK equ 02h ;index in SC of Map Mask register - SCREEN_SEG equ 0a000h ;segment of display memory in Mode X +SC_INDEX equ 03c4h ;Sequence Controller Index register port +MAP_MASK equ 02h ;index in SC of Map Mask register +SCREEN_SEG equ 0a000h ;segment of display memory in Mode X - parms struc - dw 2 dup (?) ;pushed BP and return address - SourceStartX dw ? ;X coordinate of upper-left corner of source - SourceStartY dw ? ;Y coordinate of upper-left corner of source - SourceEndX dw ? ;X coordinate of lower-right corner of source - ; (the row at EndX is not copied) - SourceEndY dw ? ;Y coordinate of lower-right corner of source - ; (the column at EndY is not copied) - DestStartX dw ? ;X coordinate of upper-left corner of dest - DestStartY dw ? ;Y coordinate of upper-left corner of dest - SourcePtr dw ? ;pointer in DS to start of bitmap in which - ; source resides - DestPageBase dw ? ;base offset in display memory of page in - ; which dest resides - SourceBitmapWidth dw ? ;# of pixels across source bitmap - DestBitmapWidth dw ? ;# of pixels across dest bitmap - ; (must be a multiple of 4) - parms ends +parms struc + dw 2 dup (?) ;pushed BP and return address +SourceStartX dw ? ;X coordinate of upper-left corner of source +SourceStartY dw ? ;Y coordinate of upper-left corner of source +SourceEndX dw ? ;X coordinate of lower-right corner of source + ; (the row at EndX is not copied) +SourceEndY dw ? ;Y coordinate of lower-right corner of source + ; (the column at EndY is not copied) +DestStartX dw ? ;X coordinate of upper-left corner of dest +DestStartY dw ? ;Y coordinate of upper-left corner of dest +SourcePtr dw ? ;pointer in DS to start of bitmap in which + ; source resides +DestPageBase dw ? ;base offset in display memory of page in + ; which dest resides +SourceBitmapWidth dw ? ;# of pixels across source bitmap +DestBitmapWidth dw ? ;# of pixels across dest bitmap + ; (must be a multiple of 4) +parms ends - RectWidth equ -2 ;local storage for width of rectangle - LeftMask equ -4 ;local storage for left rect edge plane mask - STACK_FRAME_SIZE equ 4 +RectWidth equ -2 ;local storage for width of rectangle +LeftMask equ -4 ;local storage for left rect edge plane mask +STACK_FRAME_SIZE equ 4 - .model small - .code - public _CopySystemToScreenX - _CopySystemToScreenX proc near - 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 di + .model small + .code + public _CopySystemToScreenX +_CopySystemToScreenX proc near + 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 di - cld - mov ax,SCREEN_SEG ;point ES to display memory - mov es,ax - mov ax,[bp+SourceBitmapWidth] - mul [bp+SourceStartY] ;top source rect scan line - add ax,[bp+SourceStartX] - add ax,[bp+SourcePtr] ;offset of first source rect pixel - mov si,ax ; in DS - - mov ax,[bp+DestBitmapWidth] - shr ax,1 ;convert to width in addresses - shr ax,1 - mov [bp+DestBitmapWidth],ax ;remember address width - mul [bp+DestStartY] ;top dest rect scan line - mov di,[bp+DestStartX] - mov cx,di - shr di,1 ;X/4 = offset of first dest rect pixel in - 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 - 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 - mov [bp+LeftMask],al ; plane in each nibble to 1 + cld + mov ax,SCREEN_SEG ;point ES to display memory + mov es,ax + mov ax,[bp+SourceBitmapWidth] + mul [bp+SourceStartY] ;top source rect scan line + add ax,[bp+SourceStartX] + add ax,[bp+SourcePtr] ;offset of first source rect pixel + mov si,ax ; in DS + + mov ax,[bp+DestBitmapWidth] + shr ax,1 ;convert to width in addresses + shr ax,1 + mov [bp+DestBitmapWidth],ax ;remember address width + mul [bp+DestStartY] ;top dest rect scan line + mov di,[bp+DestStartX] + mov cx,di + shr di,1 ;X/4 = offset of first dest rect pixel in + 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 + 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 + mov [bp+LeftMask],al ; plane in each nibble to 1 - mov cx,[bp+SourceEndX] ;calculate # of pixels across - sub cx,[bp+SourceStartX] ; rect - jle CopyDone ;skip if 0 or negative width - mov [bp+RectWidth],cx - mov bx,[bp+SourceEndY] - sub bx,[bp+SourceStartY] ;BX = height of rectangle - jle CopyDone ;skip if 0 or negative height - mov dx,SC_INDEX ;point to SC Index register - mov al,MAP_MASK - out dx,al ;point SC Index reg to the Map Mask - inc dx ;point DX to SC Data reg - CopyRowsLoop: - mov ax,[bp+LeftMask] - mov cx,[bp+RectWidth] - push si ;remember the start offset in the source - push di ;remember the start offset in the dest - 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 - cmc ;advance destination address only when - sbb di,0 ; wrapping from plane 3 to plane 0 - ; (else undo INC DI done by MOVSB) - loop CopyScanLineLoop - pop di ;retrieve the dest start offset - add di,[bp+DestBitmapWidth] ;point to the start of the - ; next scan line of the dest - pop si ;retrieve the source start offset - add si,[bp+SourceBitmapWidth] ;point to the start of the - ; next scan line of the source - dec bx ;count down scan lines - jnz CopyRowsLoop - CopyDone: - pop di ;restore caller's register variables - pop si - mov sp,bp ;discard storage for local variables - pop bp ;restore caller's stack frame - ret - _CopySystemToScreenX endp - end + mov cx,[bp+SourceEndX] ;calculate # of pixels across + sub cx,[bp+SourceStartX] ; rect + jle CopyDone ;skip if 0 or negative width + mov [bp+RectWidth],cx + mov bx,[bp+SourceEndY] + sub bx,[bp+SourceStartY] ;BX = height of rectangle + jle CopyDone ;skip if 0 or negative height + mov dx,SC_INDEX ;point to SC Index register + mov al,MAP_MASK + out dx,al ;point SC Index reg to the Map Mask + inc dx ;point DX to SC Data reg +CopyRowsLoop: + mov ax,[bp+LeftMask] + mov cx,[bp+RectWidth] + push si ;remember the start offset in the source + push di ;remember the start offset in the dest +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 + cmc ;advance destination address only when + sbb di,0 ; wrapping from plane 3 to plane 0 + ; (else undo INC DI done by MOVSB) + loop CopyScanLineLoop + pop di ;retrieve the dest start offset + add di,[bp+DestBitmapWidth] ;point to the start of the + ; next scan line of the dest + pop si ;retrieve the source start offset + add si,[bp+SourceBitmapWidth] ;point to the start of the + ; next scan line of the source + dec bx ;count down scan lines + jnz CopyRowsLoop +CopyDone: + pop di ;restore caller's register variables + pop si + mov sp,bp ;discard storage for local variables + pop bp ;restore caller's stack frame + ret +_CopySystemToScreenX endp + end +``` ### Who Was that Masked Image Copier? {#Heading6} diff --git a/49-01.md b/49-01.md index d426df5..b48e68e 100644 --- a/49-01.md +++ b/49-01.md @@ -75,137 +75,139 @@ and that's to use the VGA's hardware. **LISTING 49.1 L49-1.ASM** - ; Mode X (320x240, 256 colors) system memory-to-display memory masked copy - ; routine. Not particularly fast; images for which performance is critical - ; should be stored in off-screen memory and copied to screen via latches. Works - ; on all VGAs. Copies up to but not including column at SourceEndX and row at - ; SourceEndY. No clipping is performed. Mask and source image are both byte- - ; per-pixel, and must be of same widths and reside at same coordinates in their - ; respective bitmaps. Assembly code tested with TASM C near-callable as: - ; - ; void CopySystemToScreenMaskedX(int SourceStartX, - ; int SourceStartY, int SourceEndX, int SourceEndY, - ; int DestStartX, int DestStartY, char * SourcePtr, - ; unsigned int DestPageBase, int SourceBitmapWidth, - ; int DestBitmapWidth, char * MaskPtr); +```nasm +; Mode X (320x240, 256 colors) system memory-to-display memory masked copy +; routine. Not particularly fast; images for which performance is critical +; should be stored in off-screen memory and copied to screen via latches. Works +; on all VGAs. Copies up to but not including column at SourceEndX and row at +; SourceEndY. No clipping is performed. Mask and source image are both byte- +; per-pixel, and must be of same widths and reside at same coordinates in their +; respective bitmaps. Assembly code tested with TASM C near-callable as: +; +; void CopySystemToScreenMaskedX(int SourceStartX, +; int SourceStartY, int SourceEndX, int SourceEndY, +; int DestStartX, int DestStartY, char * SourcePtr, +; unsigned int DestPageBase, int SourceBitmapWidth, +; int DestBitmapWidth, char * MaskPtr); - SC_INDEX equ 03c4h ;Sequence Controller Index register port - MAP_MASK equ 02h ;index in SC of Map Mask register - SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SC_INDEX equ 03c4h ;Sequence Controller Index register port +MAP_MASK equ 02h ;index in SC of Map Mask register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X - parms struc - dw 2 dup (?) ;pushed BP and return address - SourceStartX dw ? ;X coordinate of upper left corner of source - ; (source is in system memory) - SourceStartY dw ? ;Y coordinate of upper left corner of source - SourceEndX dw ? ;X coordinate of lower right corner of source - ; (the column at EndX is not copied) - SourceEndY dw ? ;Y coordinate of lower right corner of source - ; (the row at EndY is not copied) - DestStartX dw ? ;X coordinate of upper left corner of dest - ; (destination is in display memory) - DestStartY dw ? ;Y coordinate of upper left corner of dest - SourcePtr dw ? ;pointer in DS to start of bitmap which source resides - DestPageBase dw ? ;base offset in display memory of page in - ; which dest resides - SourceBitmapWidth dw ? ;# of pixels across source bitmap (also must - ; be width across the mask) - 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 - ; pixel, 1-bytes mean do copy) - parms ends +parms struc + dw 2 dup (?) ;pushed BP and return address +SourceStartX dw ? ;X coordinate of upper left corner of source + ; (source is in system memory) +SourceStartY dw ? ;Y coordinate of upper left corner of source +SourceEndX dw ? ;X coordinate of lower right corner of source + ; (the column at EndX is not copied) +SourceEndY dw ? ;Y coordinate of lower right corner of source + ; (the row at EndY is not copied) +DestStartX dw ? ;X coordinate of upper left corner of dest + ; (destination is in display memory) +DestStartY dw ? ;Y coordinate of upper left corner of dest +SourcePtr dw ? ;pointer in DS to start of bitmap which source resides +DestPageBase dw ? ;base offset in display memory of page in + ; which dest resides +SourceBitmapWidth dw ? ;# of pixels across source bitmap (also must + ; be width across the mask) +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 + ; pixel, 1-bytes mean do copy) +parms ends - RectWidth equ -2 ;local storage for width of rectangle - RectHeight equ -4 ;local storage for height of rectangle - LeftMask equ -6 ;local storage for left rect edge plane mask - STACK_FRAME_SIZE equ 6 - .model small - .code - public _CopySystemToScreenMaskedX - _CopySystemToScreenMaskedX proc near - 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 di +RectWidth equ -2 ;local storage for width of rectangle +RectHeight equ -4 ;local storage for height of rectangle +LeftMask equ -6 ;local storage for left rect edge plane mask +STACK_FRAME_SIZE equ 6 + .model small + .code + public _CopySystemToScreenMaskedX +_CopySystemToScreenMaskedX proc near + 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 di - mov ax,SCREEN_SEG ;point ES to display memory - mov es,ax - mov ax,[bp+SourceBitmapWidth] - mul [bp+SourceStartY] ;top source rect scan line - add ax,[bp+SourceStartX] - mov bx,ax - add ax,[bp+SourcePtr] ;offset of first source rect pixel - mov si,ax ; in DS - add bx,[bp+MaskPtr] ;offset of first mask pixel in DS + mov ax,SCREEN_SEG ;point ES to display memory + mov es,ax + mov ax,[bp+SourceBitmapWidth] + mul [bp+SourceStartY] ;top source rect scan line + add ax,[bp+SourceStartX] + mov bx,ax + add ax,[bp+SourcePtr] ;offset of first source rect pixel + mov si,ax ; in DS + add bx,[bp+MaskPtr] ;offset of first mask pixel in DS - mov ax,[bp+DestBitmapWidth] - shr ax,1 ;convert to width in addresses - shr ax,1 - mov [bp+DestBitmapWidth],ax ;remember address width - mul [bp+DestStartY] ;top dest rect scan line - mov di,[bp+DestStartX] - mov cx,di - shr di,1 ;X/4 = offset of first dest rect pixel in - 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 - 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 - mov [bp+LeftMask],al ; in each nibble to 1 + mov ax,[bp+DestBitmapWidth] + shr ax,1 ;convert to width in addresses + shr ax,1 + mov [bp+DestBitmapWidth],ax ;remember address width + mul [bp+DestStartY] ;top dest rect scan line + mov di,[bp+DestStartX] + mov cx,di + shr di,1 ;X/4 = offset of first dest rect pixel in + 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 + 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 + mov [bp+LeftMask],al ; in each nibble to 1 - mov ax,[bp+SourceEndX] ;calculate # of pixels across - sub ax,[bp+SourceStartX] ; rect - jle CopyDone ;skip if 0 or negative width - mov [bp+RectWidth],ax - sub word ptr [bp+SourceBitmapWidth],ax - ;distance from end of one source scan line - to start of next - mov ax,[bp+SourceEndY] - sub ax,[bp+SourceStartY] ;height of rectangle - jle CopyDone ;skip if 0 or negative height - mov [bp+RectHeight],ax - mov dx,SC_INDEX ;point to SC Index register - mov al,MAP_MASK - out dx,al ;point SC Index reg to the Map Mask - inc dx ;point DX to SC Data reg - CopyRowsLoop: - mov al,[bp+LeftMask] - mov cx,[bp+RectWidth] - 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 - ;yes, draw the pixel - out dx,al ;set the plane for this pixel - mov ah,[si] ;get the pixel from the source - mov es:[di],ah ;copy the pixel to the screen - MaskOff: - inc bx ;advance the mask pointer - inc si ;advance the source pointer - 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 - pop di ;retrieve the dest start offset - add di,[bp+DestBitmapWidth];point to the start of the - ; next scan line of the dest - add si,[bp+SourceBitmapWidth] ;point to the start of the - ;next scan line of the source - add bx,[bp+SourceBitmapWidth] ;point to the start of the - ;next scan line of the mask - dec word ptr [bp+RectHeight] ;count down scan lines - jnz CopyRowsLoop - CopyDone: - pop di ;restore caller's register variables - pop si - mov sp,bp ;discard storage for local variables - pop bp ;restore caller's stack frame - ret - _CopySystemToScreenMaskedX endp - end + mov ax,[bp+SourceEndX] ;calculate # of pixels across + sub ax,[bp+SourceStartX] ; rect + jle CopyDone ;skip if 0 or negative width + mov [bp+RectWidth],ax + sub word ptr [bp+SourceBitmapWidth],ax + ;distance from end of one source scan line + to start of next + mov ax,[bp+SourceEndY] + sub ax,[bp+SourceStartY] ;height of rectangle + jle CopyDone ;skip if 0 or negative height + mov [bp+RectHeight],ax + mov dx,SC_INDEX ;point to SC Index register + mov al,MAP_MASK + out dx,al ;point SC Index reg to the Map Mask + inc dx ;point DX to SC Data reg +CopyRowsLoop: + mov al,[bp+LeftMask] + mov cx,[bp+RectWidth] + 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 + ;yes, draw the pixel + out dx,al ;set the plane for this pixel + mov ah,[si] ;get the pixel from the source + mov es:[di],ah ;copy the pixel to the screen +MaskOff: + inc bx ;advance the mask pointer + inc si ;advance the source pointer + 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 + pop di ;retrieve the dest start offset + add di,[bp+DestBitmapWidth];point to the start of the + ; next scan line of the dest + add si,[bp+SourceBitmapWidth] ;point to the start of the + ;next scan line of the source + add bx,[bp+SourceBitmapWidth] ;point to the start of the + ;next scan line of the mask + dec word ptr [bp+RectHeight] ;count down scan lines + jnz CopyRowsLoop +CopyDone: + pop di ;restore caller's register variables + pop si + mov sp,bp ;discard storage for local variables + pop bp ;restore caller's stack frame + ret +_CopySystemToScreenMaskedX endp + end +``` diff --git a/49-02.md b/49-02.md index 0636097..d79f404 100644 --- a/49-02.md +++ b/49-02.md @@ -45,163 +45,165 @@ alignment. **LISTING 49.2 L49-2.ASM** - ; Mode X (320x240, 256 colors) display memory to display memory masked copy - ; routine. Works on all VGAs. Uses approach of reading 4 pixels at a time from - ; source into latches, then writing latches to destination, using Map Mask - ; register to perform masking. Copies up to but not including column at - ; SourceEndX and row at SourceEndY. No clipping is performed. Results are not - ; guaranteed if source and destination overlap. C near-callable as: - ; - ; void CopyScreenToScreenMaskedX(int SourceStartX, - ; int SourceStartY, int SourceEndX, int SourceEndY, - ; int DestStartX, int DestStartY, MaskedImage * Source, - ; unsigned int DestPageBase, int DestBitmapWidth); +```nasm +; Mode X (320x240, 256 colors) display memory to display memory masked copy +; routine. Works on all VGAs. Uses approach of reading 4 pixels at a time from +; source into latches, then writing latches to destination, using Map Mask +; register to perform masking. Copies up to but not including column at +; SourceEndX and row at SourceEndY. No clipping is performed. Results are not +; guaranteed if source and destination overlap. C near-callable as: +; +; void CopyScreenToScreenMaskedX(int SourceStartX, +; int SourceStartY, int SourceEndX, int SourceEndY, +; int DestStartX, int DestStartY, MaskedImage * Source, +; unsigned int DestPageBase, int DestBitmapWidth); - SC_INDEX equ 03c4h ;Sequence Controller Index register port - MAP_MASK equ 02h ;index in SC of Map Mask register - GC_INDEX equ 03ceh ;Graphics Controller Index register port - BIT_MASK equ 08h ;index in GC of Bit Mask register - SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SC_INDEX equ 03c4h ;Sequence Controller Index register port +MAP_MASK equ 02h ;index in SC of Map Mask register +GC_INDEX equ 03ceh ;Graphics Controller Index register port +BIT_MASK equ 08h ;index in GC of Bit Mask register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X - parms struc - dw 2 dup (?) ;pushed BP and return address - SourceStartX dw ? ;X coordinate of upper left corner of source - SourceStartY dw ? ;Y coordinate of upper left corner of source - SourceEndX dw ? ;X coordinate of lower right corner of source - ; (the column at SourceEndX is not copied) - SourceEndY dw ? ;Y coordinate of lower right corner of source - ; (the row at SourceEndY is not copied) - DestStartX dw ? ;X coordinate of upper left corner of dest - DestStartY dw ? ;Y coordinate of upper left corner of dest - Source dw ? ;pointer to MaskedImage struct for source - ; which source resides - DestPageBase dw ? ;base offset in display memory of page in - ; which dest resides - DestBitmapWidth dw ? ;# of pixels across dest bitmap (must be multiple of 4) - parms ends +parms struc + dw 2 dup (?) ;pushed BP and return address +SourceStartX dw ? ;X coordinate of upper left corner of source +SourceStartY dw ? ;Y coordinate of upper left corner of source +SourceEndX dw ? ;X coordinate of lower right corner of source + ; (the column at SourceEndX is not copied) +SourceEndY dw ? ;Y coordinate of lower right corner of source + ; (the row at SourceEndY is not copied) +DestStartX dw ? ;X coordinate of upper left corner of dest +DestStartY dw ? ;Y coordinate of upper left corner of dest +Source dw ? ;pointer to MaskedImage struct for source + ; which source resides +DestPageBase dw ? ;base offset in display memory of page in + ; which dest resides +DestBitmapWidth dw ? ;# of pixels across dest bitmap (must be multiple of 4) +parms ends - SourceNextScanOffset equ -2 ;local storage for distance from end of - ; one source scan line to start of next - DestNextScanOffset equ -4 ;local storage for distance from end of - ; one dest scan line to start of next - RectAddrWidth equ -6 ;local storage for address width of rectangle - RectHeight equ -8 ;local storage for height of rectangle - SourceBitmapWidth equ -10 ;local storage for width of source bitmap - ; (in addresses) - STACK_FRAME_SIZE equ 10 - MaskedImage struc - Alignments dw 4 dup(?) ;pointers to AlignedMaskedImages for the - ; 4 possible destination image alignments - MaskedImage ends - AlignedMaskedImage struc - ImageWidth dw ? ;image width in addresses (also mask width in bytes) - ImagePtr dw ? ;offset of image bitmap in display memory - MaskPtr dw ? ;pointer to mask bitmap in DS - AlignedMaskedImage ends - .model small - .code - public _CopyScreenToScreenMaskedX - _CopyScreenToScreenMaskedX proc near - 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 di +SourceNextScanOffset equ -2 ;local storage for distance from end of + ; one source scan line to start of next +DestNextScanOffset equ -4 ;local storage for distance from end of + ; one dest scan line to start of next +RectAddrWidth equ -6 ;local storage for address width of rectangle +RectHeight equ -8 ;local storage for height of rectangle +SourceBitmapWidth equ -10 ;local storage for width of source bitmap + ; (in addresses) +STACK_FRAME_SIZE equ 10 +MaskedImage struc + Alignments dw 4 dup(?) ;pointers to AlignedMaskedImages for the + ; 4 possible destination image alignments +MaskedImage ends +AlignedMaskedImage struc + ImageWidth dw ? ;image width in addresses (also mask width in bytes) + ImagePtr dw ? ;offset of image bitmap in display memory + MaskPtr dw ? ;pointer to mask bitmap in DS +AlignedMaskedImage ends + .model small + .code + public _CopyScreenToScreenMaskedX +_CopyScreenToScreenMaskedX proc near + 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 di - cld - mov dx,GC_INDEX ;set the bit mask to select all bits - mov ax,00000h+BIT_MASK ; from the latches and none from - out dx,ax ; the CPU, so that we can write the - ; latch contents directly to memory - mov ax,SCREEN_SEG ;point ES to display memory - mov es,ax - mov ax,[bp+DestBitmapWidth] - shr ax,1 ;convert to width in addresses - shr ax,1 - mul [bp+DestStartY] ;top dest rect scan line - mov di,[bp+DestStartX] - mov si,di - shr di,1 ;X/4 = offset of first dest rect pixel in - 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 - ; aligned to match left-edge alignment - ; of destination - and si,3 ;DestStartX modulo 4 - mov cx,si ;set aside alignment for later - shl si,1 ;prepare for word look-up - mov bx,[bp+Source] ;point to source MaskedImage structure - mov bx,[bx+Alignments+si] ;point to AlignedMaskedImage - ; struc for current left edge alignment - mov ax,[bx+ImageWidth] ;image width in addresses - mov [bp+SourceBitmapWidth],ax ;remember image width in addresses - mul [bp+SourceStartY] ;top source rect scan line - mov si,[bp+SourceStartX] - shr si,1 ;X/4 = address of first source rect pixel in - shr si,1 ; scan line - add si,ax ;offset of first source rect pixel in image - mov ax,si - add si,[bx+MaskPtr] ;point to mask offset of first mask pixel in DS - mov bx,[bx+ImagePtr] ;offset of first source rect pixel - add bx,ax ; in display memory + cld + mov dx,GC_INDEX ;set the bit mask to select all bits + mov ax,00000h+BIT_MASK ; from the latches and none from + out dx,ax ; the CPU, so that we can write the + ; latch contents directly to memory + mov ax,SCREEN_SEG ;point ES to display memory + mov es,ax + mov ax,[bp+DestBitmapWidth] + shr ax,1 ;convert to width in addresses + shr ax,1 + mul [bp+DestStartY] ;top dest rect scan line + mov di,[bp+DestStartX] + mov si,di + shr di,1 ;X/4 = offset of first dest rect pixel in + 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 + ; aligned to match left-edge alignment + ; of destination + and si,3 ;DestStartX modulo 4 + mov cx,si ;set aside alignment for later + shl si,1 ;prepare for word look-up + mov bx,[bp+Source] ;point to source MaskedImage structure + mov bx,[bx+Alignments+si] ;point to AlignedMaskedImage + ; struc for current left edge alignment + mov ax,[bx+ImageWidth] ;image width in addresses + mov [bp+SourceBitmapWidth],ax ;remember image width in addresses + mul [bp+SourceStartY] ;top source rect scan line + mov si,[bp+SourceStartX] + shr si,1 ;X/4 = address of first source rect pixel in + shr si,1 ; scan line + add si,ax ;offset of first source rect pixel in image + mov ax,si + add si,[bx+MaskPtr] ;point to mask offset of first mask pixel in DS + mov bx,[bx+ImagePtr] ;offset of first source rect pixel + add bx,ax ; in display memory - mov ax,[bp+SourceStartX] ;calculate # of addresses across - add ax,cx ; rect, shifting if necessary to - add cx,[bp+SourceEndX] ; account for alignment - cmp cx,ax - jle CopyDone ;skip if 0 or negative width - add cx,3 - and ax,not 011b - sub cx,ax - shr cx,1 - shr cx,1 ;# of addresses across rectangle to copy - mov ax,[bp+SourceEndY] - sub ax,[bp+SourceStartY] ;AX = height of rectangle - jle CopyDone ;skip if 0 or negative height - mov [bp+RectHeight],ax - mov ax,[bp+DestBitmapWidth] - shr ax,1 ;convert to width in addresses - shr ax,1 - sub ax,cx ;distance from end of one dest scan line to start of next - mov [bp+DestNextScanOffset],ax - mov ax,[bp+SourceBitmapWidth] ;width in addresses - sub ax,cx ;distance from end of source scan line to start of next - mov [bp+SourceNextScanOffset],ax - mov [bp+RectAddrWidth],cx ;remember width in addresses + mov ax,[bp+SourceStartX] ;calculate # of addresses across + add ax,cx ; rect, shifting if necessary to + add cx,[bp+SourceEndX] ; account for alignment + cmp cx,ax + jle CopyDone ;skip if 0 or negative width + add cx,3 + and ax,not 011b + sub cx,ax + shr cx,1 + shr cx,1 ;# of addresses across rectangle to copy + mov ax,[bp+SourceEndY] + sub ax,[bp+SourceStartY] ;AX = height of rectangle + jle CopyDone ;skip if 0 or negative height + mov [bp+RectHeight],ax + mov ax,[bp+DestBitmapWidth] + shr ax,1 ;convert to width in addresses + shr ax,1 + sub ax,cx ;distance from end of one dest scan line to start of next + mov [bp+DestNextScanOffset],ax + mov ax,[bp+SourceBitmapWidth] ;width in addresses + sub ax,cx ;distance from end of source scan line to start of next + mov [bp+SourceNextScanOffset],ax + mov [bp+RectAddrWidth],cx ;remember width in addresses - mov dx,SC_INDEX - mov al,MAP_MASK - out dx,al ;point SC Index register to Map Mask - inc dx ;point to SC Data register - CopyRowsLoop: - mov cx,[bp+RectAddrWidth] ;width across - CopyScanLineLoop: - lodsb ;get the mask for this four-pixel set - ; and advance the mask pointer - out dx,al ;set the mask - mov al,es:[bx] ;load the latches with four-pixel set from source - mov es:[di],al ;copy the four-pixel set to the dest - inc bx ;advance the source pointer - inc di ;advance the destination pointer - dec cx ;count off four-pixel sets - jnz CopyScanLineLoop + mov dx,SC_INDEX + mov al,MAP_MASK + out dx,al ;point SC Index register to Map Mask + inc dx ;point to SC Data register +CopyRowsLoop: + mov cx,[bp+RectAddrWidth] ;width across +CopyScanLineLoop: + lodsb ;get the mask for this four-pixel set + ; and advance the mask pointer + out dx,al ;set the mask + mov al,es:[bx] ;load the latches with four-pixel set from source + mov es:[di],al ;copy the four-pixel set to the dest + inc bx ;advance the source pointer + inc di ;advance the destination pointer + dec cx ;count off four-pixel sets + jnz CopyScanLineLoop - mov ax,[bp+SourceNextScanOffset] - add si,ax ;point to the start of - add bx,ax ; the next source, mask, - add di,[bp+DestNextScanOffset] ; and dest lines - dec word ptr [bp+RectHeight] ;count down scan lines - jnz CopyRowsLoop - CopyDone: - mov dx,GC_INDEX+1 ;restore the bit mask to its default, - 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 si - mov sp,bp ;discard storage for local variables - pop bp ;restore caller's stack frame - ret - _CopyScreenToScreenMaskedX endp - end + mov ax,[bp+SourceNextScanOffset] + add si,ax ;point to the start of + add bx,ax ; the next source, mask, + add di,[bp+DestNextScanOffset] ; and dest lines + dec word ptr [bp+RectHeight] ;count down scan lines + jnz CopyRowsLoop +CopyDone: + mov dx,GC_INDEX+1 ;restore the bit mask to its default, + 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 si + mov sp,bp ;discard storage for local variables + pop bp ;restore caller's stack frame + ret +_CopyScreenToScreenMaskedX endp + end +``` diff --git a/49-03.md b/49-03.md index 70c6cbc..d51a595 100644 --- a/49-03.md +++ b/49-03.md @@ -22,92 +22,94 @@ coding speed, fewer bugs, and maintainability. **LISTING 49.3 L49-3.C** - /* Generates all four possible mode X image/mask alignments, stores image - alignments in display memory, allocates memory for and generates mask - alignments, and fills out an AlignedMaskedImage structure. Image and mask must - both be in byte-per-pixel form, and must both be of width ImageWidth. Mask - maps isomorphically (one to one) onto image, with each 0-byte in mask masking - off corresponding image pixel (causing it not to be drawn), and each non-0-byte - allowing corresponding image pixel to be drawn. Returns 0 if failure, or # of - display memory addresses (4-pixel sets) used if success. For simplicity, - allocated memory is not deallocated in case of failure. Compiled with - Borland C++ in C compilation mode. */ +```c +/* Generates all four possible mode X image/mask alignments, stores image +alignments in display memory, allocates memory for and generates mask +alignments, and fills out an AlignedMaskedImage structure. Image and mask must +both be in byte-per-pixel form, and must both be of width ImageWidth. Mask +maps isomorphically (one to one) onto image, with each 0-byte in mask masking +off corresponding image pixel (causing it not to be drawn), and each non-0-byte +allowing corresponding image pixel to be drawn. Returns 0 if failure, or # of +display memory addresses (4-pixel sets) used if success. For simplicity, +allocated memory is not deallocated in case of failure. Compiled with +Borland C++ in C compilation mode. */ - #include - #include - #include "maskim.h" +#include +#include +#include "maskim.h" - extern void CopySystemToScreenX(int, int, int, int, int, int, char *, - unsigned int, int, int); - unsigned int CreateAlignedMaskedImage(MaskedImage * ImageToSet, - unsigned int DispMemStart, char * Image, int ImageWidth, - int ImageHeight, char * Mask) - { - int Align, ScanLine, BitNum, Size, TempImageWidth; - unsigned char MaskTemp; - unsigned int DispMemOffset = DispMemStart; - AlignedMaskedImage *WorkingAMImage; - char *NewMaskPtr, *OldMaskPtr; - /* Generate each of the four alignments in turn. */ - for (Align = 0; Align < 4; Align++) { - /* Allocate space for the AlignedMaskedImage struct for this alignment. */ - if ((WorkingAMImage = ImageToSet->Alignments[Align] = - malloc(sizeof(AlignedMaskedImage))) == NULL) - return 0; - WorkingAMImage->ImageWidth = - (ImageWidth + Align + 3) / 4; /* width in 4-pixel sets */ - WorkingAMImage->ImagePtr = DispMemOffset; /* image dest */ - /* Download this alignment of the image. */ - CopySystemToScreenX(0, 0, ImageWidth, ImageHeight, Align, 0, - Image, DispMemOffset, ImageWidth, WorkingAMImage->ImageWidth * 4); - /* Calculate the number of bytes needed to store the mask in - nibble (Map Mask-ready) form, then allocate that space. */ - Size = WorkingAMImage->ImageWidth * ImageHeight; - if ((WorkingAMImage->MaskPtr = malloc(Size)) == NULL) - return 0; - /* Generate this nibble oriented (Map Mask-ready) alignment of - the mask, one scan line at a time. */ - OldMaskPtr = Mask; - NewMaskPtr = WorkingAMImage->MaskPtr; - for (ScanLine = 0; ScanLine < ImageHeight; ScanLine++) { - BitNum = Align; - MaskTemp = 0; - TempImageWidth = ImageWidth; - do { - /* Set the mask bit for next pixel according to its alignment. */ - MaskTemp |= (*OldMaskPtr++ != 0) << BitNum; - if (++BitNum > 3) { - *NewMaskPtr++ = MaskTemp; - MaskTemp = BitNum = 0; - } - } while (--TempImageWidth); - /* Set any partial final mask on this scan line. */ - if (BitNum != 0) *NewMaskPtr++ = MaskTemp; - } - DispMemOffset += Size; /* mark off the space we just used */ - } - return DispMemOffset - DispMemStart; - } +extern void CopySystemToScreenX(int, int, int, int, int, int, char *, + unsigned int, int, int); +unsigned int CreateAlignedMaskedImage(MaskedImage * ImageToSet, + unsigned int DispMemStart, char * Image, int ImageWidth, + int ImageHeight, char * Mask) +{ + int Align, ScanLine, BitNum, Size, TempImageWidth; + unsigned char MaskTemp; + unsigned int DispMemOffset = DispMemStart; + AlignedMaskedImage *WorkingAMImage; + char *NewMaskPtr, *OldMaskPtr; + /* Generate each of the four alignments in turn. */ + for (Align = 0; Align < 4; Align++) { + /* Allocate space for the AlignedMaskedImage struct for this alignment. */ + if ((WorkingAMImage = ImageToSet->Alignments[Align] = + malloc(sizeof(AlignedMaskedImage))) == NULL) + return 0; + WorkingAMImage->ImageWidth = + (ImageWidth + Align + 3) / 4; /* width in 4-pixel sets */ + WorkingAMImage->ImagePtr = DispMemOffset; /* image dest */ + /* Download this alignment of the image. */ + CopySystemToScreenX(0, 0, ImageWidth, ImageHeight, Align, 0, + Image, DispMemOffset, ImageWidth, WorkingAMImage->ImageWidth * 4); + /* Calculate the number of bytes needed to store the mask in + nibble (Map Mask-ready) form, then allocate that space. */ + Size = WorkingAMImage->ImageWidth * ImageHeight; + if ((WorkingAMImage->MaskPtr = malloc(Size)) == NULL) + return 0; + /* Generate this nibble oriented (Map Mask-ready) alignment of + the mask, one scan line at a time. */ + OldMaskPtr = Mask; + NewMaskPtr = WorkingAMImage->MaskPtr; + for (ScanLine = 0; ScanLine < ImageHeight; ScanLine++) { + BitNum = Align; + MaskTemp = 0; + TempImageWidth = ImageWidth; + do { + /* Set the mask bit for next pixel according to its alignment. */ + MaskTemp |= (*OldMaskPtr++ != 0) << BitNum; + if (++BitNum > 3) { + *NewMaskPtr++ = MaskTemp; + MaskTemp = BitNum = 0; + } + } while (--TempImageWidth); + /* Set any partial final mask on this scan line. */ + if (BitNum != 0) *NewMaskPtr++ = MaskTemp; + } + DispMemOffset += Size; /* mark off the space we just used */ + } + return DispMemOffset - DispMemStart; +} **LISTING 49.4 MASKIM.H** - /* MASKIM.H: structures used for storing and manipulating masked - images */ +/* MASKIM.H: structures used for storing and manipulating masked + images */ - /* Describes one alignment of a mask-image pair. */ - typedef struct { - int ImageWidth; /* image width in addresses in display memory (also - mask width in bytes) */ - unsigned int ImagePtr; /* offset of image bitmap in display mem */ - char *MaskPtr; /* pointer to mask bitmap */ - } AlignedMaskedImage; +/* Describes one alignment of a mask-image pair. */ +typedef struct { + int ImageWidth; /* image width in addresses in display memory (also + mask width in bytes) */ + unsigned int ImagePtr; /* offset of image bitmap in display mem */ + char *MaskPtr; /* pointer to mask bitmap */ +} AlignedMaskedImage; - /* Describes all four alignments of a mask-image pair. */ - typedef struct { - AlignedMaskedImage *Alignments[4]; /* ptrs to AlignedMaskedImage - structs for four possible destination - image alignments */ - } MaskedImage; +/* Describes all four alignments of a mask-image pair. */ +typedef struct { + AlignedMaskedImage *Alignments[4]; /* ptrs to AlignedMaskedImage + structs for four possible destination + image alignments */ +} MaskedImage; +``` #### Notes on Masked Copying {#Heading5} diff --git a/49-04.md b/49-04.md index 374fee4..b302e35 100644 --- a/49-04.md +++ b/49-04.md @@ -58,236 +58,238 @@ the resulting image—add the animation in your imagination. **LISTING 49.5 L49-5.C** - /* Sample mode X VGA animation program. Portions of this code first appeared - in PC Techniques. Compiled with Borland C++ 2.0 in C compilation mode. */ +```c +/* Sample mode X VGA animation program. Portions of this code first appeared + in PC Techniques. Compiled with Borland C++ 2.0 in C compilation mode. */ - #include - #include - #include - #include - #include "maskim.h" +#include +#include +#include +#include +#include "maskim.h" - #define SCREEN_SEG 0xA000 - #define SCREEN_WIDTH 320 - #define SCREEN_HEIGHT 240 - #define PAGE0_START_OFFSET 0 - #define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4) - #define BG_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH*2)/4) - #define DOWNLOAD_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH*3)/4) +#define SCREEN_SEG 0xA000 +#define SCREEN_WIDTH 320 +#define SCREEN_HEIGHT 240 +#define PAGE0_START_OFFSET 0 +#define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4) +#define BG_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH*2)/4) +#define DOWNLOAD_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH*3)/4) - static unsigned int PageStartOffsets[2] = {PAGE0_START_OFFSET,PAGE1_START_OFFSET}; - static char GreenAndBrownPattern[] = {2,6,2,6, 6,2,6,2, 2,6,2,6, 6,2,6,2}; - static char PineTreePattern[] = {2,2,2,2, 2,6,2,6, 2,2,6,2, 2,2,2,2}; - static char BrickPattern[] = {6,6,7,6, 7,7,7,7, 7,6,6,6, 7,7,7,7,}; - static char RoofPattern[] = {8,8,8,7, 7,7,7,7, 8,8,8,7, 8,8,8,7}; +static unsigned int PageStartOffsets[2] = {PAGE0_START_OFFSET,PAGE1_START_OFFSET}; +static char GreenAndBrownPattern[] = {2,6,2,6, 6,2,6,2, 2,6,2,6, 6,2,6,2}; +static char PineTreePattern[] = {2,2,2,2, 2,6,2,6, 2,2,6,2, 2,2,2,2}; +static char BrickPattern[] = {6,6,7,6, 7,7,7,7, 7,6,6,6, 7,7,7,7,}; +static char RoofPattern[] = {8,8,8,7, 7,7,7,7, 8,8,8,7, 8,8,8,7}; - #define SMOKE_WIDTH 7 - #define SMOKE_HEIGHT 7 - static char SmokePixels[] = { - 0, 0,15,15,15, 0, 0, - 0, 7, 7,15,15,15, 0, - 8, 7, 7, 7,15,15,15, - 8, 7, 7, 7, 7,15,15, - 0, 8, 7, 7, 7, 7,15, - 0, 0, 8, 7, 7, 7, 0, - 0, 0, 0, 8, 8, 0, 0}; - static char SmokeMask[] = { - 0, 0, 1, 1, 1, 0, 0, - 0, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, - 0, 1, 1, 1, 1, 1, 0, - 0, 0, 1, 1, 1, 0, 0}; - #define KITE_WIDTH 10 - #define KITE_HEIGHT 16 - static char KitePixels[] = { - 0, 0, 0, 0,45, 0, 0, 0, 0, 0, - 0, 0, 0,46,46,46, 0, 0, 0, 0, - 0, 0,47,47,47,47,47, 0, 0, 0, - 0,48,48,48,48,48,48,48, 0, 0, - 49,49,49,49,49,49,49,49,49, 0, - 0,50,50,50,50,50,50,50, 0, 0, - 0,51,51,51,51,51,51,51, 0, 0, - 0, 0,52,52,52,52,52, 0, 0, 0, - 0, 0,53,53,53,53,53, 0, 0, 0, - 0, 0, 0,54,54,54, 0, 0, 0, 0, - 0, 0, 0,55,55,55, 0, 0, 0, 0, - 0, 0, 0, 0,58, 0, 0, 0, 0, 0, - 0, 0, 0, 0,59, 0, 0, 0, 0,66, - 0, 0, 0, 0,60, 0, 0,64, 0,65, - 0, 0, 0, 0, 0,61, 0, 0,64, 0, - 0, 0, 0, 0, 0, 0,62,63, 0,64}; - static char KiteMask[] = { - 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, - 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, - 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, - 0, 0, 0, 0, 0, 0, 1, 1, 0, 1}; - static MaskedImage KiteImage; +#define SMOKE_WIDTH 7 +#define SMOKE_HEIGHT 7 +static char SmokePixels[] = { + 0, 0,15,15,15, 0, 0, + 0, 7, 7,15,15,15, 0, + 8, 7, 7, 7,15,15,15, + 8, 7, 7, 7, 7,15,15, + 0, 8, 7, 7, 7, 7,15, + 0, 0, 8, 7, 7, 7, 0, + 0, 0, 0, 8, 8, 0, 0}; +static char SmokeMask[] = { + 0, 0, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 0, 0}; +#define KITE_WIDTH 10 +#define KITE_HEIGHT 16 +static char KitePixels[] = { + 0, 0, 0, 0,45, 0, 0, 0, 0, 0, + 0, 0, 0,46,46,46, 0, 0, 0, 0, + 0, 0,47,47,47,47,47, 0, 0, 0, + 0,48,48,48,48,48,48,48, 0, 0, + 49,49,49,49,49,49,49,49,49, 0, + 0,50,50,50,50,50,50,50, 0, 0, + 0,51,51,51,51,51,51,51, 0, 0, + 0, 0,52,52,52,52,52, 0, 0, 0, + 0, 0,53,53,53,53,53, 0, 0, 0, + 0, 0, 0,54,54,54, 0, 0, 0, 0, + 0, 0, 0,55,55,55, 0, 0, 0, 0, + 0, 0, 0, 0,58, 0, 0, 0, 0, 0, + 0, 0, 0, 0,59, 0, 0, 0, 0,66, + 0, 0, 0, 0,60, 0, 0,64, 0,65, + 0, 0, 0, 0, 0,61, 0, 0,64, 0, + 0, 0, 0, 0, 0, 0,62,63, 0,64}; +static char KiteMask[] = { + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 1, 0, 1}; +static MaskedImage KiteImage; - #define NUM_OBJECTS 20 - typedef struct { - int X,Y,Width,Height,XDir,YDir,XOtherPage,YOtherPage; - MaskedImage *Image; - } AnimatedObject; - AnimatedObject AnimatedObjects[] = { - { 0, 0,KITE_WIDTH,KITE_HEIGHT, 1, 1, 0, 0,&KiteImage}, - { 10, 10,KITE_WIDTH,KITE_HEIGHT, 0, 1, 10, 10,&KiteImage}, - { 20, 20,KITE_WIDTH,KITE_HEIGHT,-1, 1, 20, 20,&KiteImage}, - { 30, 30,KITE_WIDTH,KITE_HEIGHT,-1,-1, 30, 30,&KiteImage}, - { 40, 40,KITE_WIDTH,KITE_HEIGHT, 1,-1, 40, 40,&KiteImage}, - { 50, 50,KITE_WIDTH,KITE_HEIGHT, 0,-1, 50, 50,&KiteImage}, - { 60, 60,KITE_WIDTH,KITE_HEIGHT, 1, 0, 60, 60,&KiteImage}, - { 70, 70,KITE_WIDTH,KITE_HEIGHT,-1, 0, 70, 70,&KiteImage}, - { 80, 80,KITE_WIDTH,KITE_HEIGHT, 1, 2, 80, 80,&KiteImage}, - { 90, 90,KITE_WIDTH,KITE_HEIGHT, 0, 2, 90, 90,&KiteImage}, - {100,100,KITE_WIDTH,KITE_HEIGHT,-1, 2,100,100,&KiteImage}, - {110,110,KITE_WIDTH,KITE_HEIGHT,-1,-2,110,110,&KiteImage}, - {120,120,KITE_WIDTH,KITE_HEIGHT, 1,-2,120,120,&KiteImage}, - {130,130,KITE_WIDTH,KITE_HEIGHT, 0,-2,130,130,&KiteImage}, - {140,140,KITE_WIDTH,KITE_HEIGHT, 2, 0,140,140,&KiteImage}, - {150,150,KITE_WIDTH,KITE_HEIGHT,-2, 0,150,150,&KiteImage}, - {160,160,KITE_WIDTH,KITE_HEIGHT, 2, 2,160,160,&KiteImage}, - {170,170,KITE_WIDTH,KITE_HEIGHT,-2, 2,170,170,&KiteImage}, - {180,180,KITE_WIDTH,KITE_HEIGHT,-2,-2,180,180,&KiteImage}, - {190,190,KITE_WIDTH,KITE_HEIGHT, 2,-2,190,190,&KiteImage}, - }; - void main(void); - void DrawBackground(unsigned int); - void MoveObject(AnimatedObject *); - extern void Set320x240Mode(void); - extern void FillRectangleX(int, int, int, int, unsigned int, int); - extern void FillPatternX(int, int, int, int, unsigned int, char*); - extern void CopySystemToScreenMaskedX(int, int, int, int, int, int, - char *, unsigned int, int, int, char *); - extern void CopyScreenToScreenX(int, int, int, int, int, int, - unsigned int, unsigned int, int, int); - extern unsigned int CreateAlignedMaskedImage(MaskedImage *, - unsigned int, char *, int, int, char *); - extern void CopyScreenToScreenMaskedX(int, int, int, int, int, int, - MaskedImage *, unsigned int, int); - extern void ShowPage(unsigned int); +#define NUM_OBJECTS 20 +typedef struct { + int X,Y,Width,Height,XDir,YDir,XOtherPage,YOtherPage; + MaskedImage *Image; +} AnimatedObject; +AnimatedObject AnimatedObjects[] = { + { 0, 0,KITE_WIDTH,KITE_HEIGHT, 1, 1, 0, 0,&KiteImage}, + { 10, 10,KITE_WIDTH,KITE_HEIGHT, 0, 1, 10, 10,&KiteImage}, + { 20, 20,KITE_WIDTH,KITE_HEIGHT,-1, 1, 20, 20,&KiteImage}, + { 30, 30,KITE_WIDTH,KITE_HEIGHT,-1,-1, 30, 30,&KiteImage}, + { 40, 40,KITE_WIDTH,KITE_HEIGHT, 1,-1, 40, 40,&KiteImage}, + { 50, 50,KITE_WIDTH,KITE_HEIGHT, 0,-1, 50, 50,&KiteImage}, + { 60, 60,KITE_WIDTH,KITE_HEIGHT, 1, 0, 60, 60,&KiteImage}, + { 70, 70,KITE_WIDTH,KITE_HEIGHT,-1, 0, 70, 70,&KiteImage}, + { 80, 80,KITE_WIDTH,KITE_HEIGHT, 1, 2, 80, 80,&KiteImage}, + { 90, 90,KITE_WIDTH,KITE_HEIGHT, 0, 2, 90, 90,&KiteImage}, + {100,100,KITE_WIDTH,KITE_HEIGHT,-1, 2,100,100,&KiteImage}, + {110,110,KITE_WIDTH,KITE_HEIGHT,-1,-2,110,110,&KiteImage}, + {120,120,KITE_WIDTH,KITE_HEIGHT, 1,-2,120,120,&KiteImage}, + {130,130,KITE_WIDTH,KITE_HEIGHT, 0,-2,130,130,&KiteImage}, + {140,140,KITE_WIDTH,KITE_HEIGHT, 2, 0,140,140,&KiteImage}, + {150,150,KITE_WIDTH,KITE_HEIGHT,-2, 0,150,150,&KiteImage}, + {160,160,KITE_WIDTH,KITE_HEIGHT, 2, 2,160,160,&KiteImage}, + {170,170,KITE_WIDTH,KITE_HEIGHT,-2, 2,170,170,&KiteImage}, + {180,180,KITE_WIDTH,KITE_HEIGHT,-2,-2,180,180,&KiteImage}, + {190,190,KITE_WIDTH,KITE_HEIGHT, 2,-2,190,190,&KiteImage}, +}; +void main(void); +void DrawBackground(unsigned int); +void MoveObject(AnimatedObject *); +extern void Set320x240Mode(void); +extern void FillRectangleX(int, int, int, int, unsigned int, int); +extern void FillPatternX(int, int, int, int, unsigned int, char*); +extern void CopySystemToScreenMaskedX(int, int, int, int, int, int, + char *, unsigned int, int, int, char *); +extern void CopyScreenToScreenX(int, int, int, int, int, int, + unsigned int, unsigned int, int, int); +extern unsigned int CreateAlignedMaskedImage(MaskedImage *, + unsigned int, char *, int, int, char *); +extern void CopyScreenToScreenMaskedX(int, int, int, int, int, int, + MaskedImage *, unsigned int, int); +extern void ShowPage(unsigned int); - void main() - { - int DisplayedPage, NonDisplayedPage, Done, i; - union REGS regset; - Set320x240Mode(); - /* Download the kite image for fast copying later. */ - 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(); - } - /* Draw the background to the background page. */ - DrawBackground(BG_START_OFFSET); - /* Copy the background to both displayable pages. */ - CopyScreenToScreenX(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, - BG_START_OFFSET, PAGE0_START_OFFSET, SCREEN_WIDTH, SCREEN_WIDTH); - CopyScreenToScreenX(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, - BG_START_OFFSET, PAGE1_START_OFFSET, SCREEN_WIDTH, SCREEN_WIDTH); - /* Move the objects and update their images in the nondisplayed - page, then flip the page, until Esc is pressed. */ - Done = DisplayedPage = 0; - do { - NonDisplayedPage = DisplayedPage ^ 1; - /* Erase each object in nondisplayed page by copying block from - background page at last location in that page. */ - for (i=0; iX + ObjectToMove->XDir; - Y = ObjectToMove->Y + ObjectToMove->YDir; - if ((X < 0) || (X > (SCREEN_WIDTH - ObjectToMove->Width))) { - ObjectToMove->XDir = -ObjectToMove->XDir; - X = ObjectToMove->X + ObjectToMove->XDir; - } - if ((Y < 0) || (Y > (SCREEN_HEIGHT - ObjectToMove->Height))) { - ObjectToMove->YDir = -ObjectToMove->YDir; - Y = ObjectToMove->Y + ObjectToMove->YDir; - } - /* Remember previous location for erasing purposes. */ - ObjectToMove->XOtherPage = ObjectToMove->X; - ObjectToMove->YOtherPage = ObjectToMove->Y; - ObjectToMove->X = X; /* set new location */ - ObjectToMove->Y = Y; - } +void main() +{ + int DisplayedPage, NonDisplayedPage, Done, i; + union REGS regset; + Set320x240Mode(); + /* Download the kite image for fast copying later. */ + 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(); + } + /* Draw the background to the background page. */ + DrawBackground(BG_START_OFFSET); + /* Copy the background to both displayable pages. */ + CopyScreenToScreenX(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, + BG_START_OFFSET, PAGE0_START_OFFSET, SCREEN_WIDTH, SCREEN_WIDTH); + CopyScreenToScreenX(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, + BG_START_OFFSET, PAGE1_START_OFFSET, SCREEN_WIDTH, SCREEN_WIDTH); + /* Move the objects and update their images in the nondisplayed + page, then flip the page, until Esc is pressed. */ + Done = DisplayedPage = 0; + do { + NonDisplayedPage = DisplayedPage ^ 1; + /* Erase each object in nondisplayed page by copying block from + background page at last location in that page. */ + for (i=0; iX + ObjectToMove->XDir; + Y = ObjectToMove->Y + ObjectToMove->YDir; + if ((X < 0) || (X > (SCREEN_WIDTH - ObjectToMove->Width))) { + ObjectToMove->XDir = -ObjectToMove->XDir; + X = ObjectToMove->X + ObjectToMove->XDir; + } + if ((Y < 0) || (Y > (SCREEN_HEIGHT - ObjectToMove->Height))) { + ObjectToMove->YDir = -ObjectToMove->YDir; + Y = ObjectToMove->Y + ObjectToMove->YDir; + } + /* Remember previous location for erasing purposes. */ + ObjectToMove->XOtherPage = ObjectToMove->X; + ObjectToMove->YOtherPage = ObjectToMove->Y; + ObjectToMove->X = X; /* set new location */ + ObjectToMove->Y = Y; +} +``` diff --git a/49-05.md b/49-05.md index 6ae09c5..d401a75 100644 --- a/49-05.md +++ b/49-05.md @@ -29,52 +29,54 @@ chapters. **LISTING 49.6 L49-6.ASM** - ; Shows the page at the specified offset in the bitmap. Page is displayed when - ; this routine returns. - ; C near-callable as: void ShowPage(unsigned int StartOffset); - INPUT_STATUS_1 equ 03dah ;Input Status 1 register - CRTC_INDEX equ 03d4h ;CRT Controller Index reg - START_ADDRESS_HIGH equ 0ch ;bitmap start address high byte - START_ADDRESS_LOW equ 0dh ;bitmap start address low byte +```nasm +; Shows the page at the specified offset in the bitmap. Page is displayed when +; this routine returns. +; C near-callable as: void ShowPage(unsigned int StartOffset); +INPUT_STATUS_1 equ 03dah ;Input Status 1 register +CRTC_INDEX equ 03d4h ;CRT Controller Index reg +START_ADDRESS_HIGH equ 0ch ;bitmap start address high byte +START_ADDRESS_LOW equ 0dh ;bitmap start address low byte - ShowPageParms struc - dw 2 dup (?) ;pushed BP and return address - StartOffset dw ? ;offset in bitmap of page to display - ShowPageParms ends - .model small - .code - public _ShowPage - _ShowPage proc near - 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. - mov bl,START_ADDRESS_LOW ;preload for fastest - mov bh,byte ptr StartOffset[bp] ; flipping once display - mov cl,START_ADDRESS_HIGH ; enable is detected - mov ch,byte ptr StartOffset+1[bp] - mov dx,INPUT_STATUS_1 - WaitDE: - in al,dx - test al,01h - jnz WaitDE ;display enable is active low (0 = active) - ; Set the start offset in display memory of the page to display. - mov dx,CRTC_INDEX - mov ax,bx - out dx,ax ;start address low - mov ax,cx - out dx,ax ;start address high - ; Now wait for vertical sync, so the other page will be invisible when - ; we start drawing to it. - mov dx,INPUT_STATUS_1 - WaitVS: - in al,dx - test al,08h - jz WaitVS ;vertical sync is active high (1 = active) - pop bp ;restore caller's stack frame - ret - _ShowPage endp - end +ShowPageParms struc + dw 2 dup (?) ;pushed BP and return address +StartOffset dw ? ;offset in bitmap of page to display +ShowPageParms ends + .model small + .code + public _ShowPage +_ShowPage proc near + 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. + mov bl,START_ADDRESS_LOW ;preload for fastest + mov bh,byte ptr StartOffset[bp] ; flipping once display + mov cl,START_ADDRESS_HIGH ; enable is detected + mov ch,byte ptr StartOffset+1[bp] + mov dx,INPUT_STATUS_1 +WaitDE: + in al,dx + test al,01h + jnz WaitDE ;display enable is active low (0 = active) +; Set the start offset in display memory of the page to display. + mov dx,CRTC_INDEX + mov ax,bx + out dx,ax ;start address low + mov ax,cx + out dx,ax ;start address high +; Now wait for vertical sync, so the other page will be invisible when +; we start drawing to it. + mov dx,INPUT_STATUS_1 +WaitVS: + in al,dx + test al,08h + jz WaitVS ;vertical sync is active high (1 = active) + pop bp ;restore caller's stack frame + ret +_ShowPage endp + end +``` ### Works Fast, Looks Great {#Heading8} diff --git a/50-03.md b/50-03.md index 24b367f..8f661ff 100644 --- a/50-03.md +++ b/50-03.md @@ -12,159 +12,161 @@ pages: 940-943 **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 - ; the rectangle specified by (ClipMinX,ClipMinY),(ClipMaxX,ClipMaxY). - ; Draws to the page specified by CurrentPageBase. - ; C near-callable as: - ; - ; void DrawHorizontalLineList(struct HLineList * HLineListPtr, - ; int Color); - ; - ; All assembly code tested with TASM and MASM +```nasm +; Draws all pixels in the list of horizontal lines passed in, in +; 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: +; +; void DrawHorizontalLineList(struct HLineList * HLineListPtr, +; int Color); +; +; All assembly code tested with TASM and MASM - SCREEN_WIDTH equ 320 - SCREEN_SEGMENT equ 0a000h - SC_INDEX equ 03c4h ;Sequence Controller Index - MAP_MASK equ 2 ;Map Mask register index in SC +SCREEN_WIDTH equ 320 +SCREEN_SEGMENT equ 0a000h +SC_INDEX equ 03c4h ;Sequence Controller Index +MAP_MASK equ 2 ;Map Mask register index in SC - HLine struc - XStart dw ? ;X coordinate of leftmost pixel in line - XEnd dw ? ;X coordinate of rightmost pixel in line - HLine ends +HLine struc +XStart dw ? ;X coordinate of leftmost pixel in line +XEnd dw ? ;X coordinate of rightmost pixel in line +HLine ends - HLineList struc - Lngth dw ? ;# of horizontal lines - YStart dw ? ;Y coordinate of topmost line - HLinePtr dw ? ;pointer to list of horz lines - HLineList ends +HLineList struc +Lngth dw ? ;# of horizontal lines +YStart dw ? ;Y coordinate of topmost line +HLinePtr dw ? ;pointer to list of horz lines +HLineList ends - Parms struc - dw 2 dup(?) ;return address & pushed BP - HLineListPtr dw ? ;pointer to HLineList structure - Color dw ? ;color with which to fill - Parms ends - .model small - .data - extrn _CurrentPageBase:word,_ClipMinX:word - extrn _ClipMinY:word,_ClipMaxX:word,_ClipMaxY:word - ; Plane masks for clipping left and right edges of rectangle. - LeftClipPlaneMask db 00fh,00eh,00ch,008h - RightClipPlaneMask db 001h,003h,007h,00fh - .code - align 2 - ToFillDone: - jmp FillDone - public _DrawHorizontalLineList - align 2 - _DrawHorizontalLineList proc - push bp ;preserve caller's stack frame - mov bp,sp ;point to our stack frame - push si ;preserve caller's register variables - push di - cld ;make string instructions inc pointers - mov dx,SC_INDEX - mov al,MAP_MASK - out dx,al ;point SC Index to the Map Mask - mov ax,SCREEN_SEGMENT - mov es,ax ;point ES to display memory for REP STOS - mov si,[bp+HLineListPtr] ;point to the line list - mov bx,[si+HLinePtr] ;point to the XStart/XEnd descriptor - ; for the first (top) horizontal line - 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 - cmp cx,[_ClipMinY] ;clipped at top? - jge MinYNotClipped ;no - neg cx ;yes, discard however many lines are - add cx,[_ClipMinY] ; clipped - sub si,cx ;that many fewer lines to draw - jle ToFillDone ;no lines left to draw - shl cx,1 ;lines to skip*2 - shl cx,1 ;lines to skip*4 - add bx,cx ;advance through the line list - mov cx,[_ClipMinY] ;start at the top clip line - MinYNotClipped: - mov dx,si - add dx,cx ;bottom row to draw + 1 - cmp dx,[_ClipMaxY] ;clipped at bottom? - jle MaxYNotClipped ;no - sub dx,[_ClipMaxY] ;# of lines to clip off the bottom - sub si,dx ;# of lines left to draw - jle ToFillDone ;all lines are clipped - MaxYNotClipped: - mov ax,SCREEN_WIDTH/4 ;point to the start of the first - mul cx ; scan line on which to draw - add ax,[_CurrentPageBase] ;offset of first line - mov dx,ax ;ES:DX points to first scan line to draw - mov ah,byte ptr [bp+Color] ;color with which to fill - FillLoop: - push bx ;remember line list location - push dx ;remember offset of start of line - push si ;remember # of lines to draw - mov di,[bx+XStart] ;left edge of fill on this line - cmp di,[_ClipMinX] ;clipped to left edge? - jge MinXNotClipped ;no - mov di,[_ClipMinX] ;yes, clip to the left edge - MinXNotClipped: - mov si,di - mov cx,[bx+XEnd] ;right edge of fill - cmp cx,[_ClipMaxX] ;clipped to right edge? - jl MaxXNotClipped ;no - mov cx,[_ClipMaxX] ;yes, clip to the right edge - dec cx - MaxXNotClipped: - cmp cx,di - jl LineFillDone ;skip if negative width - shr di,1 ;X/4 = offset of first rect pixel in scan - shr di,1 ; line - add di,dx ;offset of first rect pixel in display mem - mov dx,si ;XStart - and si,0003h ;look up left-edge plane mask - mov bh,LeftClipPlaneMask[si] ; to clip & put in BH - mov si,cx - and si,0003h ;look up right-edge plane - mov bl,RightClipPlaneMask[si] ; mask to clip & put in BL - and dx,not 011b ;calculate # of addresses across rect - 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 - ; and right edge clip masks - MasksSet: - mov dx,SC_INDEX+1 ;already points to the Map Mask reg - FillRowsLoop: - mov al,bh ;put left-edge clip mask in AL - out dx,al ;set the left-edge plane (clip) mask - 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 - 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 - mov al,ah ;put color in AL - rep stosb ;draw the middle addresses four pixels apiece - DoRightEdge: - mov al,bl ;put right-edge clip mask in AL - out dx,al ;set the right-edge plane (clip) mask - mov al,ah ;put color in AL - stosb ;draw the right edge - FillLoopBottom: - LineFillDone: - pop si ;retrieve # of lines to draw - pop dx ;retrieve offset of start of line - pop bx ;retrieve line list location - add dx,SCREEN_WIDTH/4 ;point to start of next line - add bx,size HLine ;point to the next line descriptor - dec si ;count down lines - jnz FillLoop - FillDone: - pop di ;restore caller's register variables - pop si - pop bp ;restore caller's stack frame - ret - _DrawHorizontalLineList endp - end +Parms struc + dw 2 dup(?) ;return address & pushed BP +HLineListPtr dw ? ;pointer to HLineList structure +Color dw ? ;color with which to fill +Parms ends + .model small + .data + extrn _CurrentPageBase:word,_ClipMinX:word + extrn _ClipMinY:word,_ClipMaxX:word,_ClipMaxY:word +; Plane masks for clipping left and right edges of rectangle. +LeftClipPlaneMask db 00fh,00eh,00ch,008h +RightClipPlaneMask db 001h,003h,007h,00fh + .code + align 2 +ToFillDone: + jmp FillDone + public _DrawHorizontalLineList + align 2 +_DrawHorizontalLineList proc + push bp ;preserve caller's stack frame + mov bp,sp ;point to our stack frame + push si ;preserve caller's register variables + push di + cld ;make string instructions inc pointers + mov dx,SC_INDEX + mov al,MAP_MASK + out dx,al ;point SC Index to the Map Mask + mov ax,SCREEN_SEGMENT + mov es,ax ;point ES to display memory for REP STOS + mov si,[bp+HLineListPtr] ;point to the line list + mov bx,[si+HLinePtr] ;point to the XStart/XEnd descriptor + ; for the first (top) horizontal line + 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 + cmp cx,[_ClipMinY] ;clipped at top? + jge MinYNotClipped ;no + neg cx ;yes, discard however many lines are + add cx,[_ClipMinY] ; clipped + sub si,cx ;that many fewer lines to draw + jle ToFillDone ;no lines left to draw + shl cx,1 ;lines to skip*2 + shl cx,1 ;lines to skip*4 + add bx,cx ;advance through the line list + mov cx,[_ClipMinY] ;start at the top clip line +MinYNotClipped: + mov dx,si + add dx,cx ;bottom row to draw + 1 + cmp dx,[_ClipMaxY] ;clipped at bottom? + jle MaxYNotClipped ;no + sub dx,[_ClipMaxY] ;# of lines to clip off the bottom + sub si,dx ;# of lines left to draw + jle ToFillDone ;all lines are clipped +MaxYNotClipped: + mov ax,SCREEN_WIDTH/4 ;point to the start of the first + mul cx ; scan line on which to draw + add ax,[_CurrentPageBase] ;offset of first line + mov dx,ax ;ES:DX points to first scan line to draw + mov ah,byte ptr [bp+Color] ;color with which to fill +FillLoop: + push bx ;remember line list location + push dx ;remember offset of start of line + push si ;remember # of lines to draw + mov di,[bx+XStart] ;left edge of fill on this line + cmp di,[_ClipMinX] ;clipped to left edge? + jge MinXNotClipped ;no + mov di,[_ClipMinX] ;yes, clip to the left edge +MinXNotClipped: + mov si,di + mov cx,[bx+XEnd] ;right edge of fill + cmp cx,[_ClipMaxX] ;clipped to right edge? + jl MaxXNotClipped ;no + mov cx,[_ClipMaxX] ;yes, clip to the right edge + dec cx +MaxXNotClipped: + cmp cx,di + jl LineFillDone ;skip if negative width + shr di,1 ;X/4 = offset of first rect pixel in scan + shr di,1 ; line + add di,dx ;offset of first rect pixel in display mem + mov dx,si ;XStart + and si,0003h ;look up left-edge plane mask + mov bh,LeftClipPlaneMask[si] ; to clip & put in BH + mov si,cx + and si,0003h ;look up right-edge plane + mov bl,RightClipPlaneMask[si] ; mask to clip & put in BL + and dx,not 011b ;calculate # of addresses across rect + 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 + ; and right edge clip masks +MasksSet: + mov dx,SC_INDEX+1 ;already points to the Map Mask reg +FillRowsLoop: + mov al,bh ;put left-edge clip mask in AL + out dx,al ;set the left-edge plane (clip) mask + 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 + 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 + mov al,ah ;put color in AL + rep stosb ;draw the middle addresses four pixels apiece +DoRightEdge: + mov al,bl ;put right-edge clip mask in AL + out dx,al ;set the right-edge plane (clip) mask + mov al,ah ;put color in AL + stosb ;draw the right edge +FillLoopBottom: +LineFillDone: + pop si ;retrieve # of lines to draw + pop dx ;retrieve offset of start of line + pop bx ;retrieve line list location + add dx,SCREEN_WIDTH/4 ;point to start of next line + add bx,size HLine ;point to the next line descriptor + dec si ;count down lines + jnz FillLoop +FillDone: + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame + ret +_DrawHorizontalLineList endp + end +``` diff --git a/50-04.md b/50-04.md index 973a138..e67ab2a 100644 --- a/50-04.md +++ b/50-04.md @@ -35,103 +35,107 @@ but it will certainly reduce confusion! **LISTING 50.2 L50-2.C** - /* Matrix arithmetic functions. - Tested with Borland C++ in the small model. */ +```c +/* Matrix arithmetic functions. + Tested with Borland C++ in the small model. */ - /* 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, as follows: - -- -- -- -- -- -- - | | | 4 | | 4 | - | 4x4 | X | x | = | x | - | | | 1 | | 1 | - -- -- -- -- -- -- */ - void XformVec(double Xform[4][4], double * SourceVec, - double * DestVec) - { - int i,j; +/* 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, as follows: + -- -- -- -- -- -- + | | | 4 | | 4 | + | 4x4 | X | x | = | x | + | | | 1 | | 1 | + -- -- -- -- -- -- */ +void XformVec(double Xform[4][4], double * SourceVec, + double * DestVec) +{ + int i,j; - for (i=0; i<4; i++) { - DestVec[i] = 0; - for (j=0; j<4; j++) - DestVec[i] += Xform[i][j] * SourceVec[j]; - } - } + for (i=0; i<4; i++) { + DestVec[i] = 0; + for (j=0; j<4; j++) + DestVec[i] += Xform[i][j] * SourceVec[j]; + } +} - /* 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, as follows: - -- -- -- -- -- -- - | | | | | | - | 4x4 | X | 4x4 | = | 4x4 | - | | | | | | - -- -- -- -- -- -- */ - void ConcatXforms(double SourceXform1[4][4], double SourceXform2[4][4], - double DestXform[4][4]) - { - int i,j,k; +/* 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, as follows: + -- -- -- -- -- -- + | | | | | | + | 4x4 | X | 4x4 | = | 4x4 | + | | | | | | + -- -- -- -- -- -- */ +void ConcatXforms(double SourceXform1[4][4], double SourceXform2[4][4], + double DestXform[4][4]) +{ + int i,j,k; - for (i=0; i<4; i++) { - for (j=0; j<4; j++) { - DestXform[i][j] = 0; - for (k=0; k<4; k++) - DestXform[i][j] += SourceXform1[i][k] * SourceXform2[k][j]; - } - } - } + for (i=0; i<4; i++) { + for (j=0; j<4; j++) { + DestXform[i][j] = 0; + for (k=0; k<4; k++) + DestXform[i][j] += SourceXform1[i][k] * SourceXform2[k][j]; + } + } +} +``` **LISTING 50.3 L50-3.C** - /* Transforms convex polygon Poly (which has PolyLength vertices), - performing the transformation according to Xform (which generally - 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. - Tested with Borland C++ in the small model. */ - #include "polygon.h" +```c +/* Transforms convex polygon Poly (which has PolyLength vertices), + performing the transformation according to Xform (which generally + 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. + Tested with Borland C++ in the small model. */ +#include "polygon.h" - void XformAndProjectPoly(double Xform[4][4], struct Point3 * Poly, - int PolyLength, int Color) - { - int i; - struct Point3 XformedPoly[MAX_POLY_LENGTH]; - struct Point ProjectedPoly[MAX_POLY_LENGTH]; - struct PointListHeader Polygon; +void XformAndProjectPoly(double Xform[4][4], struct Point3 * Poly, + int PolyLength, int Color) +{ + int i; + struct Point3 XformedPoly[MAX_POLY_LENGTH]; + struct Point ProjectedPoly[MAX_POLY_LENGTH]; + struct PointListHeader Polygon; - /* Transform to view space, then project to the screen */ - for (i=0; i EraseRect[NonDisplayedPage].Right) - if (ProjectedPoly[i].X < SCREEN_WIDTH) - EraseRect[NonDisplayedPage].Right = ProjectedPoly[i].X; - else EraseRect[NonDisplayedPage].Right = SCREEN_WIDTH; - if (ProjectedPoly[i].Y > EraseRect[NonDisplayedPage].Bottom) - if (ProjectedPoly[i].Y < SCREEN_HEIGHT) - EraseRect[NonDisplayedPage].Bottom = ProjectedPoly[i].Y; - else EraseRect[NonDisplayedPage].Bottom = SCREEN_HEIGHT; - if (ProjectedPoly[i].X < EraseRect[NonDisplayedPage].Left) - if (ProjectedPoly[i].X > 0) - EraseRect[NonDisplayedPage].Left = ProjectedPoly[i].X; - else EraseRect[NonDisplayedPage].Left = 0; - if (ProjectedPoly[i].Y < EraseRect[NonDisplayedPage].Top) - if (ProjectedPoly[i].Y > 0) - EraseRect[NonDisplayedPage].Top = ProjectedPoly[i].Y; - else EraseRect[NonDisplayedPage].Top = 0; - } - /* Draw the polygon */ - DRAW_POLYGON(ProjectedPoly, PolyLength, Color, 0, 0); - } + /* Transform to view space, then project to the screen */ + for (i=0; i EraseRect[NonDisplayedPage].Right) + if (ProjectedPoly[i].X < SCREEN_WIDTH) + EraseRect[NonDisplayedPage].Right = ProjectedPoly[i].X; + else EraseRect[NonDisplayedPage].Right = SCREEN_WIDTH; + if (ProjectedPoly[i].Y > EraseRect[NonDisplayedPage].Bottom) + if (ProjectedPoly[i].Y < SCREEN_HEIGHT) + EraseRect[NonDisplayedPage].Bottom = ProjectedPoly[i].Y; + else EraseRect[NonDisplayedPage].Bottom = SCREEN_HEIGHT; + if (ProjectedPoly[i].X < EraseRect[NonDisplayedPage].Left) + if (ProjectedPoly[i].X > 0) + EraseRect[NonDisplayedPage].Left = ProjectedPoly[i].X; + else EraseRect[NonDisplayedPage].Left = 0; + if (ProjectedPoly[i].Y < EraseRect[NonDisplayedPage].Top) + if (ProjectedPoly[i].Y > 0) + EraseRect[NonDisplayedPage].Top = ProjectedPoly[i].Y; + else EraseRect[NonDisplayedPage].Top = 0; + } + /* Draw the polygon */ + DRAW_POLYGON(ProjectedPoly, PolyLength, Color, 0, 0); +} +``` diff --git a/50-05.md b/50-05.md index 1c2e886..a7ab9ff 100644 --- a/50-05.md +++ b/50-05.md @@ -12,75 +12,77 @@ pages: 945-946 **LISTING 50.4 POLYGON.H** - /* POLYGON.H: Header file for polygon-filling code, also includes - a number of useful items for 3-D animation. */ +```c +/* POLYGON.H: Header file for polygon-filling code, also includes + a number of useful items for 3-D animation. */ - #define MAX_POLY_LENGTH 4 /* four vertices is the max per poly */ - #define SCREEN_WIDTH 320 - #define SCREEN_HEIGHT 240 - #define PAGE0_START_OFFSET 0 - #define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4) - /* Ratio: distance from viewpoint to projection plane / width of - projection plane. Defines the width of the field of view. Lower - absolute values = wider fields of view; higher values = narrower */ - #define PROJECTION_RATIO -2.0 /* negative because visible Z - coordinates are negative */ - /* Draws the polygon described by the point list PointList in color - Color with all vertices offset by (X,Y) */ - #define DRAW_POLYGON(PointList,NumPoints,Color,X,Y) \ - Polygon.Length = NumPoints; \ - Polygon.PointPtr = PointList; \ - FillConvexPolygon(&Polygon, Color, X, Y); +#define MAX_POLY_LENGTH 4 /* four vertices is the max per poly */ +#define SCREEN_WIDTH 320 +#define SCREEN_HEIGHT 240 +#define PAGE0_START_OFFSET 0 +#define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4) +/* Ratio: distance from viewpoint to projection plane / width of + projection plane. Defines the width of the field of view. Lower + absolute values = wider fields of view; higher values = narrower */ +#define PROJECTION_RATIO -2.0 /* negative because visible Z + coordinates are negative */ +/* Draws the polygon described by the point list PointList in color + Color with all vertices offset by (X,Y) */ +#define DRAW_POLYGON(PointList,NumPoints,Color,X,Y) \ + Polygon.Length = NumPoints; \ + Polygon.PointPtr = PointList; \ + FillConvexPolygon(&Polygon, Color, X, Y); - /* Describes a single 2-D point */ - struct Point { - int X; /* X coordinate */ - int Y; /* Y coordinate */ - }; - /* Describes a single 3-D point in homogeneous coordinates */ - struct Point3 { - double X; /* X coordinate */ - double Y; /* Y coordinate */ - double Z; /* Z coordinate */ - double W; - }; - /* Describes a series of points (used to store a list of vertices that - describe a polygon; each vertex is assumed to connect to the two - adjacent vertices, and the last vertex is assumed to connect to the - first) */ - struct PointListHeader { - int Length; /* # of points */ - struct Point * PointPtr; /* pointer to list of points */ - }; +/* Describes a single 2-D point */ +struct Point { + int X; /* X coordinate */ + int Y; /* Y coordinate */ +}; +/* Describes a single 3-D point in homogeneous coordinates */ +struct Point3 { + double X; /* X coordinate */ + double Y; /* Y coordinate */ + double Z; /* Z coordinate */ + double W; +}; +/* Describes a series of points (used to store a list of vertices that + describe a polygon; each vertex is assumed to connect to the two + adjacent vertices, and the last vertex is assumed to connect to the + first) */ +struct PointListHeader { + int Length; /* # of points */ + struct Point * PointPtr; /* pointer to list of points */ +}; - /* Describes the beginning and ending X coordinates of a single - horizontal line */ - struct HLine { - int XStart; /* X coordinate of leftmost pixel in line */ - int XEnd; /* X coordinate of rightmost pixel in line */ - }; +/* Describes the beginning and ending X coordinates of a single + horizontal line */ +struct HLine { + int XStart; /* X coordinate of leftmost pixel in line */ + int XEnd; /* X coordinate of rightmost pixel in line */ +}; - /* Describes a Length-long series of horizontal lines, all assumed to - be on contiguous scan lines starting at YStart and proceeding - downward (used to describe a scan-converted polygon to the - low-level hardware-dependent drawing code) */ - struct HLineList { - int Length; /* # of horizontal lines */ - int YStart; /* Y coordinate of topmost line */ - struct HLine * HLinePtr; /* pointer to list of horz lines */ - }; - struct Rect { int Left, Top, Right, Bottom; }; +/* Describes a Length-long series of horizontal lines, all assumed to + be on contiguous scan lines starting at YStart and proceeding + downward (used to describe a scan-converted polygon to the + low-level hardware-dependent drawing code) */ +struct HLineList { + int Length; /* # of horizontal lines */ + int YStart; /* Y coordinate of topmost line */ + struct HLine * HLinePtr; /* pointer to list of horz lines */ +}; +struct Rect { int Left, Top, Right, Bottom; }; - extern void XformVec(double Xform[4][4], double * SourceVec, - double * DestVec); - extern void ConcatXforms(double SourceXform1[4][4], - double SourceXform2[4][4], double DestXform[4][4]); - extern void XformAndProjectPoly(double Xform[4][4], - struct Point3 * Poly, int PolyLength, int Color); - extern int FillConvexPolygon(struct PointListHeader *, int, int, int); - extern void Set320x240Mode(void); - extern void ShowPage(unsigned int StartOffset); - extern void FillRectangleX(int StartX, int StartY, int EndX, - int EndY, unsigned int PageBase, int Color); - extern int DisplayedPage, NonDisplayedPage; - extern struct Rect EraseRect[]; +extern void XformVec(double Xform[4][4], double * SourceVec, + double * DestVec); +extern void ConcatXforms(double SourceXform1[4][4], + double SourceXform2[4][4], double DestXform[4][4]); +extern void XformAndProjectPoly(double Xform[4][4], + struct Point3 * Poly, int PolyLength, int Color); +extern int FillConvexPolygon(struct PointListHeader *, int, int, int); +extern void Set320x240Mode(void); +extern void ShowPage(unsigned int StartOffset); +extern void FillRectangleX(int StartX, int StartY, int EndX, + int EndY, unsigned int PageBase, int Color); +extern int DisplayedPage, NonDisplayedPage; +extern struct Rect EraseRect[]; +``` diff --git a/50-06.md b/50-06.md index 814b794..4cba017 100644 --- a/50-06.md +++ b/50-06.md @@ -12,123 +12,125 @@ pages: 946-948 **LISTING 50.5 L50-5.C** - /* Simple 3-D drawing program to view a polygon as it rotates in - Mode X. View space is congruent with world space, with the - viewpoint fixed at the origin (0,0,0) of world space, looking in - the direction of increasingly negative Z. A right-handed - coordinate system is used throughout. - Tested with Borland C++ in the small model. */ - #include - #include - #include - #include - #include "polygon.h" - void main(void); +```c +/* Simple 3-D drawing program to view a polygon as it rotates in + Mode X. View space is congruent with world space, with the + viewpoint fixed at the origin (0,0,0) of world space, looking in + the direction of increasingly negative Z. A right-handed + coordinate system is used throughout. + Tested with Borland C++ in the small model. */ +#include +#include +#include +#include +#include "polygon.h" +void main(void); - /* Base offset of page to which to draw */ - unsigned int CurrentPageBase = 0; - /* Clip rectangle; clips to the screen */ - int ClipMinX=0, ClipMinY=0; - int ClipMaxX=SCREEN_WIDTH, ClipMaxY=SCREEN_HEIGHT; - /* 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. - 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 - straight ahead in the direction of view. The program dynamically - changes the rotation and translation. */ - static double PolyWorldXform[4][4] = { - {1.0, 0.0, 0.0, 0.0}, - {0.0, 1.0, 0.0, 0.0}, - {0.0, 0.0, 1.0, -140.0}, - {0.0, 0.0, 0.0, 1.0} }; - /* Transformation from world space into view space. In this program, - the view point is fixed at the origin of world space, looking down - the Z axis in the direction of increasingly negative Z, so view - space is identical to world space; this is the identity matrix. */ - static double WorldViewXform[4][4] = { - {1.0, 0.0, 0.0, 0.0}, - {0.0, 1.0, 0.0, 0.0}, - {0.0, 0.0, 1.0, 0.0}, - {0.0, 0.0, 0.0, 1.0} - }; - static unsigned int PageStartOffsets[2] = - {PAGE0_START_OFFSET,PAGE1_START_OFFSET}; - int DisplayedPage, NonDisplayedPage; +/* Base offset of page to which to draw */ +unsigned int CurrentPageBase = 0; +/* Clip rectangle; clips to the screen */ +int ClipMinX=0, ClipMinY=0; +int ClipMaxX=SCREEN_WIDTH, ClipMaxY=SCREEN_HEIGHT; +/* 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. + 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 + straight ahead in the direction of view. The program dynamically + changes the rotation and translation. */ +static double PolyWorldXform[4][4] = { + {1.0, 0.0, 0.0, 0.0}, + {0.0, 1.0, 0.0, 0.0}, + {0.0, 0.0, 1.0, -140.0}, + {0.0, 0.0, 0.0, 1.0} }; +/* Transformation from world space into view space. In this program, + the view point is fixed at the origin of world space, looking down + the Z axis in the direction of increasingly negative Z, so view + space is identical to world space; this is the identity matrix. */ +static double WorldViewXform[4][4] = { + {1.0, 0.0, 0.0, 0.0}, + {0.0, 1.0, 0.0, 0.0}, + {0.0, 0.0, 1.0, 0.0}, + {0.0, 0.0, 0.0, 1.0} +}; +static unsigned int PageStartOffsets[2] = + {PAGE0_START_OFFSET,PAGE1_START_OFFSET}; +int DisplayedPage, NonDisplayedPage; - void main() { - int Done = 0; - double WorkingXform[4][4]; - static struct Point3 TestPoly[] = - {{-30,-15,0,1},{0,15,0,1},{10,-5,0,1}}; - #define TEST_POLY_LENGTH (sizeof(TestPoly)/sizeof(struct Point3)) - double Rotation = M_PI / 60.0; /* initial rotation = 3 degrees */ - union REGS regset; +void main() { + int Done = 0; + double WorkingXform[4][4]; + static struct Point3 TestPoly[] = + {{-30,-15,0,1},{0,15,0,1},{10,-5,0,1}}; +#define TEST_POLY_LENGTH (sizeof(TestPoly)/sizeof(struct Point3)) + double Rotation = M_PI / 60.0; /* initial rotation = 3 degrees */ + union REGS regset; - Set320x240Mode(); - ShowPage(PageStartOffsets[DisplayedPage = 0]); - /* Keep rotating the polygon, drawing it to the undisplayed page, - and flipping the page to show it */ - do { - CurrentPageBase = /* select other page for drawing to */ - PageStartOffsets[NonDisplayedPage = DisplayedPage ^ 1]; - /* Modify the object space to world space transformation matrix - for the current rotation around the Y axis */ - PolyWorldXform[0][0] = PolyWorldXform[2][2] = cos(Rotation); - PolyWorldXform[2][0] = -(PolyWorldXform[0][2] = sin(Rotation)); - /* Concatenate the object-to-world and world-to-view - transformations to make a transformation matrix that will - convert vertices from object space to view space in a single - operation */ - ConcatXforms(WorldViewXform, PolyWorldXform, WorkingXform); - /* Clear the portion of the non-displayed page that was drawn - to last time, then reset the erase extent */ - FillRectangleX(EraseRect[NonDisplayedPage].Left, - EraseRect[NonDisplayedPage].Top, - EraseRect[NonDisplayedPage].Right, - EraseRect[NonDisplayedPage].Bottom, CurrentPageBase, 0); - EraseRect[NonDisplayedPage].Left = - EraseRect[NonDisplayedPage].Top = 0x7FFF; - EraseRect[NonDisplayedPage].Right = - EraseRect[NonDisplayedPage].Bottom = 0; - /* Transform the polygon, project it on the screen, draw it */ - XformAndProjectPoly(WorkingXform, TestPoly, TEST_POLY_LENGTH,9); - /* Flip to display the page into which we just drew */ - ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]); - /* Rotate 6 degrees farther around the Y axis */ - if ((Rotation += (M_PI/30.0)) >= (M_PI*2)) Rotation -= M_PI*2; - if (kbhit()) { - switch (getch()) { - case 0x1B: /* Esc to exit */ - Done = 1; break; - 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 */ - if (PolyWorldXform[2][3] < -40.0) - PolyWorldXform[2][3] += 3.0; break; - case 0: /* extended code */ - switch (getch()) { - case 0x4B: /* left (-X) */ - PolyWorldXform[0][3] -= 3.0; break; - case 0x4D: /* right (+X) */ - PolyWorldXform[0][3] += 3.0; break; - case 0x48: /* up (+Y) */ - PolyWorldXform[1][3] += 3.0; break; - case 0x50: /* down (-Y) */ - PolyWorldXform[1][3] -= 3.0; break; - default: - break; - } - break; - default: /* any other key to pause */ - getch(); break; - } - } - } while (!Done); - /* Return to text mode and exit */ - regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */ - int86(0x10, ®set, ®set); - } + Set320x240Mode(); + ShowPage(PageStartOffsets[DisplayedPage = 0]); + /* Keep rotating the polygon, drawing it to the undisplayed page, + and flipping the page to show it */ + do { + CurrentPageBase = /* select other page for drawing to */ + PageStartOffsets[NonDisplayedPage = DisplayedPage ^ 1]; + /* Modify the object space to world space transformation matrix + for the current rotation around the Y axis */ + PolyWorldXform[0][0] = PolyWorldXform[2][2] = cos(Rotation); + PolyWorldXform[2][0] = -(PolyWorldXform[0][2] = sin(Rotation)); + /* Concatenate the object-to-world and world-to-view + transformations to make a transformation matrix that will + convert vertices from object space to view space in a single + operation */ + ConcatXforms(WorldViewXform, PolyWorldXform, WorkingXform); + /* Clear the portion of the non-displayed page that was drawn + to last time, then reset the erase extent */ + FillRectangleX(EraseRect[NonDisplayedPage].Left, + EraseRect[NonDisplayedPage].Top, + EraseRect[NonDisplayedPage].Right, + EraseRect[NonDisplayedPage].Bottom, CurrentPageBase, 0); + EraseRect[NonDisplayedPage].Left = + EraseRect[NonDisplayedPage].Top = 0x7FFF; + EraseRect[NonDisplayedPage].Right = + EraseRect[NonDisplayedPage].Bottom = 0; + /* Transform the polygon, project it on the screen, draw it */ + XformAndProjectPoly(WorkingXform, TestPoly, TEST_POLY_LENGTH,9); + /* Flip to display the page into which we just drew */ + ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]); + /* Rotate 6 degrees farther around the Y axis */ + if ((Rotation += (M_PI/30.0)) >= (M_PI*2)) Rotation -= M_PI*2; + if (kbhit()) { + switch (getch()) { + case 0x1B: /* Esc to exit */ + Done = 1; break; + 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 */ + if (PolyWorldXform[2][3] < -40.0) + PolyWorldXform[2][3] += 3.0; break; + case 0: /* extended code */ + switch (getch()) { + case 0x4B: /* left (-X) */ + PolyWorldXform[0][3] -= 3.0; break; + case 0x4D: /* right (+X) */ + PolyWorldXform[0][3] += 3.0; break; + case 0x48: /* up (+Y) */ + PolyWorldXform[1][3] += 3.0; break; + case 0x50: /* down (-Y) */ + PolyWorldXform[1][3] -= 3.0; break; + default: + break; + } + break; + default: /* any other key to pause */ + getch(); break; + } + } + } while (!Done); + /* Return to text mode and exit */ + regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */ + int86(0x10, ®set, ®set); +} +``` diff --git a/51-03.md b/51-03.md index 649faf9..73e184f 100644 --- a/51-03.md +++ b/51-03.md @@ -12,163 +12,165 @@ pages: 957-960 **LISTING 51.1 L51-1.C** - /* 3D animation program to view a cube as it rotates in Mode X. The viewpoint - is fixed at the origin (0,0,0) of world space, looking in the direction of - increasingly negative Z. A right-handed coordinate system is used throughout. - All C code tested with Borland C++ in C compilation mode. */ - #include - #include - #include - #include "polygon.h" +```c +/* 3D animation program to view a cube as it rotates in Mode X. The viewpoint + is fixed at the origin (0,0,0) of world space, looking in the direction of + increasingly negative Z. A right-handed coordinate system is used throughout. + All C code tested with Borland C++ in C compilation mode. */ +#include +#include +#include +#include "polygon.h" - #define ROTATION (M_PI / 30.0) /* rotate by 6 degrees at a time */ +#define ROTATION (M_PI / 30.0) /* rotate by 6 degrees at a time */ - /* base offset of page to which to draw */ - unsigned int CurrentPageBase = 0; - /* Clip rectangle; clips to the screen */ - int ClipMinX=0, ClipMinY=0; - int ClipMaxX=SCREEN_WIDTH, ClipMaxY=SCREEN_HEIGHT; - /* 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} }; - static unsigned int PageStartOffsets[2] = - {PAGE0_START_OFFSET,PAGE1_START_OFFSET}; - int DisplayedPage, NonDisplayedPage; - /* 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 - direction of view. The program dynamically changes both the - translation and the rotation. */ - static double CubeWorldXform[4][4] = { - {1.0, 0.0, 0.0, 0.0}, - {0.0, 1.0, 0.0, 0.0}, - {0.0, 0.0, 1.0, -100.0}, - {0.0, 0.0, 0.0, 1.0} }; - /* Transformation from world space into view space. Because in this - application the view point is fixed at the origin of world space, - looking down the Z axis in the direction of increasing Z, view space is - identical to world space, and this is the identity matrix. */ - static double WorldViewXform[4][4] = { - {1.0, 0.0, 0.0, 0.0}, - {0.0, 1.0, 0.0, 0.0}, - {0.0, 0.0, 1.0, 0.0}, - {0.0, 0.0, 0.0, 1.0} - }; - /* all vertices in the cube */ - static struct Point3 CubeVerts[] = { - {15,15,15,1},{15,15,-15,1},{15,-15,15,1},{15,-15,-15,1}, - {-15,15,15,1},{-15,15,-15,1},{-15,-15,15,1},{-15,-15,-15,1}}; - /* vertices after transformation */ - static struct Point3 - XformedCubeVerts[sizeof(CubeVerts)/sizeof(struct Point3)]; - /* vertices after projection */ - static struct Point3 - ProjectedCubeVerts[sizeof(CubeVerts)/sizeof(struct Point3)]; - /* vertices in screen coordinates */ - static struct Point - ScreenCubeVerts[sizeof(CubeVerts)/sizeof(struct Point3)]; - /* vertex indices for individual faces */ - static int Face1[] = {1,3,2,0}; - static int Face2[] = {5,7,3,1}; - static int Face3[] = {4,5,1,0}; - static int Face4[] = {3,7,6,2}; - static int Face5[] = {5,4,6,7}; - static int Face6[] = {0,2,6,4}; - /* list of cube faces */ - static struct Face CubeFaces[] = {{Face1,4,15},{Face2,4,14}, - {Face3,4,12},{Face4,4,11},{Face5,4,10},{Face6,4,9}}; - /* master description for cube */ - static struct Object Cube = {sizeof(CubeVerts)/sizeof(struct Point3), - CubeVerts, XformedCubeVerts, ProjectedCubeVerts, ScreenCubeVerts, - sizeof(CubeFaces)/sizeof(struct Face), CubeFaces}; +/* base offset of page to which to draw */ +unsigned int CurrentPageBase = 0; +/* Clip rectangle; clips to the screen */ +int ClipMinX=0, ClipMinY=0; +int ClipMaxX=SCREEN_WIDTH, ClipMaxY=SCREEN_HEIGHT; +/* 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} }; +static unsigned int PageStartOffsets[2] = + {PAGE0_START_OFFSET,PAGE1_START_OFFSET}; +int DisplayedPage, NonDisplayedPage; +/* 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 + direction of view. The program dynamically changes both the + translation and the rotation. */ +static double CubeWorldXform[4][4] = { + {1.0, 0.0, 0.0, 0.0}, + {0.0, 1.0, 0.0, 0.0}, + {0.0, 0.0, 1.0, -100.0}, + {0.0, 0.0, 0.0, 1.0} }; +/* Transformation from world space into view space. Because in this + application the view point is fixed at the origin of world space, + looking down the Z axis in the direction of increasing Z, view space is + identical to world space, and this is the identity matrix. */ +static double WorldViewXform[4][4] = { + {1.0, 0.0, 0.0, 0.0}, + {0.0, 1.0, 0.0, 0.0}, + {0.0, 0.0, 1.0, 0.0}, + {0.0, 0.0, 0.0, 1.0} +}; +/* all vertices in the cube */ +static struct Point3 CubeVerts[] = { + {15,15,15,1},{15,15,-15,1},{15,-15,15,1},{15,-15,-15,1}, + {-15,15,15,1},{-15,15,-15,1},{-15,-15,15,1},{-15,-15,-15,1}}; +/* vertices after transformation */ +static struct Point3 + XformedCubeVerts[sizeof(CubeVerts)/sizeof(struct Point3)]; +/* vertices after projection */ +static struct Point3 + ProjectedCubeVerts[sizeof(CubeVerts)/sizeof(struct Point3)]; +/* vertices in screen coordinates */ +static struct Point + ScreenCubeVerts[sizeof(CubeVerts)/sizeof(struct Point3)]; +/* vertex indices for individual faces */ +static int Face1[] = {1,3,2,0}; +static int Face2[] = {5,7,3,1}; +static int Face3[] = {4,5,1,0}; +static int Face4[] = {3,7,6,2}; +static int Face5[] = {5,4,6,7}; +static int Face6[] = {0,2,6,4}; +/* list of cube faces */ +static struct Face CubeFaces[] = {{Face1,4,15},{Face2,4,14}, + {Face3,4,12},{Face4,4,11},{Face5,4,10},{Face6,4,9}}; +/* master description for cube */ +static struct Object Cube = {sizeof(CubeVerts)/sizeof(struct Point3), + CubeVerts, XformedCubeVerts, ProjectedCubeVerts, ScreenCubeVerts, + sizeof(CubeFaces)/sizeof(struct Face), CubeFaces}; - void main() { - int Done = 0, RecalcXform = 1; - double WorkingXform[4][4]; - union REGS regset; +void main() { + int Done = 0, RecalcXform = 1; + double WorkingXform[4][4]; + union REGS regset; - /* Set up the initial transformation */ - Set320x240Mode(); /* set the screen to Mode X */ - ShowPage(PageStartOffsets[DisplayedPage = 0]); - /* Keep transforming the cube, drawing it to the undisplayed page, - and flipping the page to show it */ - do { - /* Regenerate the object->view transformation and - retransform/project if necessary */ - if (RecalcXform) { - ConcatXforms(WorldViewXform, CubeWorldXform, WorkingXform); - /* Transform and project all the vertices in the cube */ - XformAndProjectPoints(WorkingXform, &Cube); - RecalcXform = 0; - } - CurrentPageBase = /* select other page for drawing to */ - PageStartOffsets[NonDisplayedPage = DisplayedPage ^ 1]; - /* Clear the portion of the non-displayed page that was drawn - to last time, then reset the erase extent */ - FillRectangleX(EraseRect[NonDisplayedPage].Left, - EraseRect[NonDisplayedPage].Top, - EraseRect[NonDisplayedPage].Right, - EraseRect[NonDisplayedPage].Bottom, CurrentPageBase, 0); - EraseRect[NonDisplayedPage].Left = - EraseRect[NonDisplayedPage].Top = 0x7FFF; - EraseRect[NonDisplayedPage].Right = - EraseRect[NonDisplayedPage].Bottom = 0; - /* Draw all visible faces of the cube */ - DrawVisibleFaces(&Cube); - /* Flip to display the page into which we just drew */ - ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]); - while (kbhit()) { - switch (getch()) { - case 0x1B: /* Esc to exit */ - Done = 1; break; - 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 */ - if (CubeWorldXform[2][3] < -40.0) { - CubeWorldXform[2][3] += 3.0; - RecalcXform = 1; - } - break; - case ‘4': /* rotate clockwise around Y */ - AppendRotationY(CubeWorldXform, -ROTATION); - RecalcXform=1; break; - case ‘6': /* rotate counterclockwise around Y */ - AppendRotationY(CubeWorldXform, ROTATION); - RecalcXform=1; break; - case ‘8': /* rotate clockwise around X */ - AppendRotationX(CubeWorldXform, -ROTATION); - RecalcXform=1; break; - case ‘2': /* rotate counterclockwise around X */ - AppendRotationX(CubeWorldXform, ROTATION); - RecalcXform=1; break; - case 0: /* extended code */ - switch (getch()) { - case 0x3B: /* rotate counterclockwise around Z */ - AppendRotationZ(CubeWorldXform, ROTATION); - RecalcXform=1; break; - case 0x3C: /* rotate clockwise around Z */ - AppendRotationZ(CubeWorldXform, -ROTATION); - RecalcXform=1; break; - case 0x4B: /* left (-X) */ - CubeWorldXform[0][3] -= 3.0; RecalcXform=1; break; - case 0x4D: /* right (+X) */ - CubeWorldXform[0][3] += 3.0; RecalcXform=1; break; - case 0x48: /* up (+Y) */ - CubeWorldXform[1][3] += 3.0; RecalcXform=1; break; - case 0x50: /* down (-Y) */ - CubeWorldXform[1][3] -= 3.0; RecalcXform=1; break; - default: - break; - } - break; - default: /* any other key to pause */ - getch(); break; - } - } - } while (!Done); - /* Return to text mode and exit */ - regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */ - int86(0x10, ®set, ®set); - } + /* Set up the initial transformation */ + Set320x240Mode(); /* set the screen to Mode X */ + ShowPage(PageStartOffsets[DisplayedPage = 0]); + /* Keep transforming the cube, drawing it to the undisplayed page, + and flipping the page to show it */ + do { + /* Regenerate the object->view transformation and + retransform/project if necessary */ + if (RecalcXform) { + ConcatXforms(WorldViewXform, CubeWorldXform, WorkingXform); + /* Transform and project all the vertices in the cube */ + XformAndProjectPoints(WorkingXform, &Cube); + RecalcXform = 0; + } + CurrentPageBase = /* select other page for drawing to */ + PageStartOffsets[NonDisplayedPage = DisplayedPage ^ 1]; + /* Clear the portion of the non-displayed page that was drawn + to last time, then reset the erase extent */ + FillRectangleX(EraseRect[NonDisplayedPage].Left, + EraseRect[NonDisplayedPage].Top, + EraseRect[NonDisplayedPage].Right, + EraseRect[NonDisplayedPage].Bottom, CurrentPageBase, 0); + EraseRect[NonDisplayedPage].Left = + EraseRect[NonDisplayedPage].Top = 0x7FFF; + EraseRect[NonDisplayedPage].Right = + EraseRect[NonDisplayedPage].Bottom = 0; + /* Draw all visible faces of the cube */ + DrawVisibleFaces(&Cube); + /* Flip to display the page into which we just drew */ + ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]); + while (kbhit()) { + switch (getch()) { + case 0x1B: /* Esc to exit */ + Done = 1; break; + 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 */ + if (CubeWorldXform[2][3] < -40.0) { + CubeWorldXform[2][3] += 3.0; + RecalcXform = 1; + } + break; + case ‘4': /* rotate clockwise around Y */ + AppendRotationY(CubeWorldXform, -ROTATION); + RecalcXform=1; break; + case ‘6': /* rotate counterclockwise around Y */ + AppendRotationY(CubeWorldXform, ROTATION); + RecalcXform=1; break; + case ‘8': /* rotate clockwise around X */ + AppendRotationX(CubeWorldXform, -ROTATION); + RecalcXform=1; break; + case ‘2': /* rotate counterclockwise around X */ + AppendRotationX(CubeWorldXform, ROTATION); + RecalcXform=1; break; + case 0: /* extended code */ + switch (getch()) { + case 0x3B: /* rotate counterclockwise around Z */ + AppendRotationZ(CubeWorldXform, ROTATION); + RecalcXform=1; break; + case 0x3C: /* rotate clockwise around Z */ + AppendRotationZ(CubeWorldXform, -ROTATION); + RecalcXform=1; break; + case 0x4B: /* left (-X) */ + CubeWorldXform[0][3] -= 3.0; RecalcXform=1; break; + case 0x4D: /* right (+X) */ + CubeWorldXform[0][3] += 3.0; RecalcXform=1; break; + case 0x48: /* up (+Y) */ + CubeWorldXform[1][3] += 3.0; RecalcXform=1; break; + case 0x50: /* down (-Y) */ + CubeWorldXform[1][3] -= 3.0; RecalcXform=1; break; + default: + break; + } + break; + default: /* any other key to pause */ + getch(); break; + } + } + } while (!Done); + /* Return to text mode and exit */ + regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */ + int86(0x10, ®set, ®set); +} +``` diff --git a/51-04.md b/51-04.md index f54757a..cb3228d 100644 --- a/51-04.md +++ b/51-04.md @@ -12,98 +12,102 @@ pages: 960-963 **LISTING 51.2 L51-2.C** - /* Transforms all vertices in the specified object into view spa ce, then - perspective projects them to screen space and maps them to screen coordinates, - storing the results in the object. */ - #include - #include "polygon.h"/ +```c +/* Transforms all vertices in the specified object into view spa ce, then + perspective projects them to screen space and maps them to screen coordinates, + storing the results in the object. */ +#include +#include "polygon.h"/ - void XformAndProjectPoints(double Xform[4][4], - struct Object * ObjectToXform) - { - int i, NumPoints = ObjectToXform->NumVerts; - struct Point3 * Points = ObjectToXform->VertexList; - struct Point3 * XformedPoints = ObjectToXform->XformedVertexList; - struct Point3 * ProjectedPoints = ObjectToXform->ProjectedVertexList; - struct Point * ScreenPoints = ObjectToXform->ScreenVertexList; +void XformAndProjectPoints(double Xform[4][4], + struct Object * ObjectToXform) +{ + int i, NumPoints = ObjectToXform->NumVerts; + struct Point3 * Points = ObjectToXform->VertexList; + struct Point3 * XformedPoints = ObjectToXform->XformedVertexList; + struct Point3 * ProjectedPoints = ObjectToXform->ProjectedVertexList; + struct Point * ScreenPoints = ObjectToXform->ScreenVertexList; - for (i=0; iX = XformedPoints->X / XformedPoints->Z * - PROJECTION_RATIO * (SCREEN_WIDTH / 2.0); - ProjectedPoints->Y = XformedPoints->Y / XformedPoints->Z * - PROJECTION_RATIO * (SCREEN_WIDTH / 2.0); - ProjectedPoints->Z = XformedPoints->Z; - /* Convert to screen coordinates. The Y coord is negated to - flip from increasing Y being up to increasing Y being down, - as expected by the polygon filler. Add in half the screen - width and height to center on the screen. */ - ScreenPoints->X = ((int) floor(ProjectedPoints->X + 0.5)) + SCREEN_WIDTH/2; - ScreenPoints->Y = (-((int) floor(ProjectedPoints->Y + 0.5))) + - SCREEN_HEIGHT/2; - } - } + for (i=0; iX = XformedPoints->X / XformedPoints->Z * + PROJECTION_RATIO * (SCREEN_WIDTH / 2.0); + ProjectedPoints->Y = XformedPoints->Y / XformedPoints->Z * + PROJECTION_RATIO * (SCREEN_WIDTH / 2.0); + ProjectedPoints->Z = XformedPoints->Z; + /* Convert to screen coordinates. The Y coord is negated to + flip from increasing Y being up to increasing Y being down, + as expected by the polygon filler. Add in half the screen + width and height to center on the screen. */ + ScreenPoints->X = ((int) floor(ProjectedPoints->X + 0.5)) + SCREEN_WIDTH/2; + ScreenPoints->Y = (-((int) floor(ProjectedPoints->Y + 0.5))) + + SCREEN_HEIGHT/2; + } +} +``` **LISTING 51.3 L51-3.C** - /* 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" +```c +/* 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" - void DrawVisibleFaces(struct Object * ObjectToXform) - { - int i, j, NumFaces = ObjectToXform->NumFaces, NumVertices; - int * VertNumsPtr; - struct Face * FacePtr = ObjectToXform->FaceList; - struct Point * ScreenPoints = ObjectToXform->ScreenVertexList; - long v1,v2,w1,w2; - struct Point Vertices[MAX_POLY_LENGTH]; - struct PointListHeader Polygon; +void DrawVisibleFaces(struct Object * ObjectToXform) +{ + int i, j, NumFaces = ObjectToXform->NumFaces, NumVertices; + int * VertNumsPtr; + struct Face * FacePtr = ObjectToXform->FaceList; + struct Point * ScreenPoints = ObjectToXform->ScreenVertexList; + long v1,v2,w1,w2; + struct Point Vertices[MAX_POLY_LENGTH]; + struct PointListHeader Polygon; - /* Draw each visible face (polygon) of the object in turn */ - for (i=0; iNumVerts; - /* Copy over the face's vertices from the vertex list */ - for (j=0, VertNumsPtr=FacePtr->VertNums; j 0) { - /* It is facing the screen, so draw */ - /* Appropriately adjust the extent of the rectangle used to - erase this page later */ - for (j=0; j EraseRect[NonDisplayedPage].Right) - if (Vertices[j].X < SCREEN_WIDTH) - EraseRect[NonDisplayedPage].Right = Vertices[j].X; - else EraseRect[NonDisplayedPage].Right = SCREEN_WIDTH; - if (Vertices[j].Y > EraseRect[NonDisplayedPage].Bottom) - if (Vertices[j].Y < SCREEN_HEIGHT) - EraseRect[NonDisplayedPage].Bottom = Vertices[j].Y; - else EraseRect[NonDisplayedPage].Bottom=SCREEN_HEIGHT; - if (Vertices[j].X < EraseRect[NonDisplayedPage].Left) - if (Vertices[j].X > 0) - EraseRect[NonDisplayedPage].Left = Vertices[j].X; - else EraseRect[NonDisplayedPage].Left = 0; - if (Vertices[j].Y < EraseRect[NonDisplayedPage].Top) - if (Vertices[j].Y > 0) - EraseRect[NonDisplayedPage].Top = Vertices[j].Y; - else EraseRect[NonDisplayedPage].Top = 0; - } - /* Draw the polygon */ - DRAW_POLYGON(Vertices, NumVertices, FacePtr->Color, 0, 0); - } - } - } + /* Draw each visible face (polygon) of the object in turn */ + for (i=0; iNumVerts; + /* Copy over the face's vertices from the vertex list */ + for (j=0, VertNumsPtr=FacePtr->VertNums; j 0) { + /* It is facing the screen, so draw */ + /* Appropriately adjust the extent of the rectangle used to + erase this page later */ + for (j=0; j EraseRect[NonDisplayedPage].Right) + if (Vertices[j].X < SCREEN_WIDTH) + EraseRect[NonDisplayedPage].Right = Vertices[j].X; + else EraseRect[NonDisplayedPage].Right = SCREEN_WIDTH; + if (Vertices[j].Y > EraseRect[NonDisplayedPage].Bottom) + if (Vertices[j].Y < SCREEN_HEIGHT) + EraseRect[NonDisplayedPage].Bottom = Vertices[j].Y; + else EraseRect[NonDisplayedPage].Bottom=SCREEN_HEIGHT; + if (Vertices[j].X < EraseRect[NonDisplayedPage].Left) + if (Vertices[j].X > 0) + EraseRect[NonDisplayedPage].Left = Vertices[j].X; + else EraseRect[NonDisplayedPage].Left = 0; + if (Vertices[j].Y < EraseRect[NonDisplayedPage].Top) + if (Vertices[j].Y > 0) + EraseRect[NonDisplayedPage].Top = Vertices[j].Y; + else EraseRect[NonDisplayedPage].Top = 0; + } + /* Draw the polygon */ + DRAW_POLYGON(Vertices, NumVertices, FacePtr->Color, 0, 0); + } + } +} +``` The sample program, as shown in Figure 51.3, places a cube, floating in three-space, under the complete control of the user. The arrow keys may diff --git a/51-05.md b/51-05.md index adc2a46..5befa8f 100644 --- a/51-05.md +++ b/51-05.md @@ -46,64 +46,66 @@ eventually. **LISTING 51.4 L51-4.C** - /* Routines to perform incremental rotations around the three axes */ - #include - #include "polygon.h" +```c +/* Routines to perform incremental rotations around the three axes */ +#include +#include "polygon.h" - /* Concatenate a rotation by Angle around the X axis to the transformation in - XformToChange, placing result back in XformToChange. */ - void AppendRotationX(double XformToChange[4][4], double Angle) - { - double Temp10, Temp11, Temp12, Temp20, Temp21, Temp22; - double CosTemp = cos(Angle), SinTemp = sin(Angle); - /* Calculate the new values of the four affected matrix entries */ - Temp10 = CosTemp*XformToChange[1][0]+ -SinTemp*XformToChange[2][0]; - Temp11 = CosTemp*XformToChange[1][1]+ -SinTemp*XformToChange[2][1]; - Temp12 = CosTemp*XformToChange[1][2]+ -SinTemp*XformToChange[2][2]; - Temp20 = SinTemp*XformToChange[1][0]+ CosTemp*XformToChange[2][0]; - Temp21 = SinTemp*XformToChange[1][1]+ CosTemp*XformToChange[2][1]; - Temp22 = SinTemp*XformToChange[1][2]+ CosTemp*XformToChange[2][2]; - /* Put the results back into XformToChange */ - XformToChange[1][0] = Temp10; XformToChange[1][1] = Temp11; - XformToChange[1][2] = Temp12; XformToChange[2][0] = Temp20; - XformToChange[2][1] = Temp21; XformToChange[2][2] = Temp22; - } +/* Concatenate a rotation by Angle around the X axis to the transformation in + XformToChange, placing result back in XformToChange. */ + void AppendRotationX(double XformToChange[4][4], double Angle) +{ + double Temp10, Temp11, Temp12, Temp20, Temp21, Temp22; + double CosTemp = cos(Angle), SinTemp = sin(Angle); + /* Calculate the new values of the four affected matrix entries */ + Temp10 = CosTemp*XformToChange[1][0]+ -SinTemp*XformToChange[2][0]; + Temp11 = CosTemp*XformToChange[1][1]+ -SinTemp*XformToChange[2][1]; + Temp12 = CosTemp*XformToChange[1][2]+ -SinTemp*XformToChange[2][2]; + Temp20 = SinTemp*XformToChange[1][0]+ CosTemp*XformToChange[2][0]; + Temp21 = SinTemp*XformToChange[1][1]+ CosTemp*XformToChange[2][1]; + Temp22 = SinTemp*XformToChange[1][2]+ CosTemp*XformToChange[2][2]; + /* Put the results back into XformToChange */ + XformToChange[1][0] = Temp10; XformToChange[1][1] = Temp11; + XformToChange[1][2] = Temp12; XformToChange[2][0] = Temp20; + XformToChange[2][1] = Temp21; XformToChange[2][2] = Temp22; +} - /* Concatenate a rotation by Angle around the Y axis to the transformation in - XformToChange, placing result back in XformToChange. */ - void AppendRotationY(double XformToChange[4][4], double Angle) - { - double Temp00, Temp01, Temp02, Temp20, Temp21, Temp22; - double CosTemp = cos(Angle), SinTemp = sin(Angle); +/* Concatenate a rotation by Angle around the Y axis to the transformation in + XformToChange, placing result back in XformToChange. */ + void AppendRotationY(double XformToChange[4][4], double Angle) +{ + double Temp00, Temp01, Temp02, Temp20, Temp21, Temp22; + double CosTemp = cos(Angle), SinTemp = sin(Angle); - /* Calculate the new values of the four affected matrix entries */ - Temp00 = CosTemp*XformToChange[0][0]+ SinTemp*XformToChange[2][0]; - Temp01 = CosTemp*XformToChange[0][1]+ SinTemp*XformToChange[2][1]; - Temp02 = CosTemp*XformToChange[0][2]+ SinTemp*XformToChange[2][2]; - Temp20 = -SinTemp*XformToChange[0][0]+ CosTemp*XformToChange[2][0]; - Temp21 = -SinTemp*XformToChange[0][1]+ CosTemp*XformToChange[2][1]; - Temp22 = -SinTemp*XformToChange[0][2]+ CosTemp*XformToChange[2][2]; - /* Put the results back into XformToChange */ - XformToChange[0][0] = Temp00; XformToChange[0][1] = Temp01; - XformToChange[0][2] = Temp02; XformToChange[2][0] = Temp20; - XformToChange[2][1] = Temp21; XformToChange[2][2] = Temp22; - } + /* Calculate the new values of the four affected matrix entries */ + Temp00 = CosTemp*XformToChange[0][0]+ SinTemp*XformToChange[2][0]; + Temp01 = CosTemp*XformToChange[0][1]+ SinTemp*XformToChange[2][1]; + Temp02 = CosTemp*XformToChange[0][2]+ SinTemp*XformToChange[2][2]; + Temp20 = -SinTemp*XformToChange[0][0]+ CosTemp*XformToChange[2][0]; + Temp21 = -SinTemp*XformToChange[0][1]+ CosTemp*XformToChange[2][1]; + Temp22 = -SinTemp*XformToChange[0][2]+ CosTemp*XformToChange[2][2]; + /* Put the results back into XformToChange */ + XformToChange[0][0] = Temp00; XformToChange[0][1] = Temp01; + XformToChange[0][2] = Temp02; XformToChange[2][0] = Temp20; + XformToChange[2][1] = Temp21; XformToChange[2][2] = Temp22; +} - /* Concatenate a rotation by Angle around the Z axis to the transformation in - XformToChange, placing result back in XformToChange. */ - void AppendRotationZ(double XformToChange[4][4], double Angle) - { - double Temp00, Temp01, Temp02, Temp10, Temp11, Temp12; - double CosTemp = cos(Angle), SinTemp = sin(Angle); - /* Calculate the new values of the four affected matrix entries */ - Temp00 = CosTemp*XformToChange[0][0]+ -SinTemp*XformToChange[1][0]; - Temp01 = CosTemp*XformToChange[0][1]+ -SinTemp*XformToChange[1][1]; - Temp02 = CosTemp*XformToChange[0][2]+ -SinTemp*XformToChange[1][2]; - Temp10 = SinTemp*XformToChange[0][0]+ CosTemp*XformToChange[1][0]; - Temp11 = SinTemp*XformToChange[0][1]+ CosTemp*XformToChange[1][1]; - Temp12 = SinTemp*XformToChange[0][2]+ CosTemp*XformToChange[1][2]; - /* Put the results back into XformToChange */ - XformToChange[0][0] = Temp00; XformToChange[0][1] = Temp01; - XformToChange[0][2] = Temp02; XformToChange[1][0] = Temp10; - XformToChange[1][1] = Temp11; XformToChange[1][2] = Temp12; - } +/* Concatenate a rotation by Angle around the Z axis to the transformation in + XformToChange, placing result back in XformToChange. */ + void AppendRotationZ(double XformToChange[4][4], double Angle) +{ + double Temp00, Temp01, Temp02, Temp10, Temp11, Temp12; + double CosTemp = cos(Angle), SinTemp = sin(Angle); + /* Calculate the new values of the four affected matrix entries */ + Temp00 = CosTemp*XformToChange[0][0]+ -SinTemp*XformToChange[1][0]; + Temp01 = CosTemp*XformToChange[0][1]+ -SinTemp*XformToChange[1][1]; + Temp02 = CosTemp*XformToChange[0][2]+ -SinTemp*XformToChange[1][2]; + Temp10 = SinTemp*XformToChange[0][0]+ CosTemp*XformToChange[1][0]; + Temp11 = SinTemp*XformToChange[0][1]+ CosTemp*XformToChange[1][1]; + Temp12 = SinTemp*XformToChange[0][2]+ CosTemp*XformToChange[1][2]; + /* Put the results back into XformToChange */ + XformToChange[0][0] = Temp00; XformToChange[0][1] = Temp01; + XformToChange[0][2] = Temp02; XformToChange[1][0] = Temp10; + XformToChange[1][1] = Temp11; XformToChange[1][2] = Temp12; +} +``` diff --git a/51-06.md b/51-06.md index 5c55658..496eeb5 100644 --- a/51-06.md +++ b/51-06.md @@ -12,88 +12,90 @@ pages: 965-967 **LISTING 51.5 POLYGON.H** - /* POLYGON.H: Header file for polygon-filling code, also includes a number of - useful items for 3D animation. */ - #define MAX_POLY_LENGTH 4 /* four vertices is the max per poly */ - #define SCREEN_WIDTH 320 - #define SCREEN_HEIGHT 240 - #define PAGE0_START_OFFSET 0 - #define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4) - /* Ratio: distance from viewpoint to projection plane / width of projection - plane. Defines the width of the field of view. Lower absolute values = wider - fields of view; higher values = narrower. */ - #define PROJECTION_RATIO -2.0 /* negative because visible Z coordinates are negative */ - /* Draws the polygon described by the point list PointList in color Color with - all vertices offset by (X,Y) */ - #define DRAW_POLYGON(PointList,NumPoints,Color,X,Y) \ - Polygon.Length = NumPoints; Polygon.PointPtr = PointList; \ - FillConvexPolygon(&Polygon, Color, X, Y); - /* Describes a single 2D point */ - struct Point { - int X; /* X coordinate */ - int Y; /* Y coordinate */ - }; - /* Describes a single 3D point in homogeneous coordinates */ - struct Point3 { - double X; /* X coordinate */ - double Y; /* Y coordinate */ - double Z; /* Z coordinate */ - double W; - }; - /* Describes a series of points (used to store a list of vertices that - describe a polygon; each vertex is assumed to connect to the two adjacent - vertices, and the last vertex is assumed to connect to the first) */ - struct PointListHeader { - int Length; /* # of points */ - struct Point * PointPtr; /* pointer to list of points */ - }; - /* Describes beginning and ending X coordinates of a single horizontal line */ - struct HLine { - int XStart; /* X coordinate of leftmost pixel in line */ - int XEnd; /* X coordinate of rightmost pixel in line */ - }; - /* Describes a Length-long series of horizontal lines, all assumed to be on - contiguous scan lines starting at YStart and proceeding downward (describes - a scan-converted polygon to low-level hardware-dependent drawing code) */ - struct HLineList { - int Length; /* # of horizontal lines */ - int YStart; /* Y coordinate of topmost line */ - struct HLine * HLinePtr; /* pointer to list of horz lines */ - }; - struct Rect { int Left, Top, Right, Bottom; }; - /* Structure describing one face of an object (one polygon) */ - struct Face { - int * VertNums; /* pointer to vertex ptrs */ - int NumVerts; /* # of vertices */ - int Color; /* polygon color */ - }; - /* Structure describing an object */ - struct Object { - int NumVerts; - struct Point3 * VertexList; - struct Point3 * XformedVertexList; - struct Point3 * ProjectedVertexList; - struct Point * ScreenVertexList; - int NumFaces; - struct Face * FaceList; - }; - extern void XformVec(double Xform[4][4], double * SourceVec, double * DestVec); - extern void ConcatXforms(double SourceXform1[4][4], - double SourceXform2[4][4], double DestXform[4][4]); - extern void XformAndProjectPoly(double Xform[4][4], - struct Point3 * Poly, int PolyLength, int Color); - extern int FillConvexPolygon(struct PointListHeader *, int, int, int); - extern void Set320x240Mode(void); - extern void ShowPage(unsigned int StartOffset); - extern void FillRectangleX(int StartX, int StartY, int EndX, - int EndY, unsigned int PageBase, int Color); - extern void XformAndProjectPoints(double Xform[4][4],struct Object * ObjectToXform); - extern void DrawVisibleFaces(struct Object * ObjectToXform); - extern void AppendRotationX(double XformToChange[4][4], double Angle); - extern void AppendRotationY(double XformToChange[4][4], double Angle); - extern void AppendRotationZ(double XformToChange[4][4], double Angle); - extern int DisplayedPage, NonDisplayedPage; - extern struct Rect EraseRect[]; +```c +/* POLYGON.H: Header file for polygon-filling code, also includes a number of + useful items for 3D animation. */ +#define MAX_POLY_LENGTH 4 /* four vertices is the max per poly */ +#define SCREEN_WIDTH 320 +#define SCREEN_HEIGHT 240 +#define PAGE0_START_OFFSET 0 +#define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4) +/* Ratio: distance from viewpoint to projection plane / width of projection + plane. Defines the width of the field of view. Lower absolute values = wider + fields of view; higher values = narrower. */ +#define PROJECTION_RATIO -2.0 /* negative because visible Z coordinates are negative */ +/* Draws the polygon described by the point list PointList in color Color with + all vertices offset by (X,Y) */ +#define DRAW_POLYGON(PointList,NumPoints,Color,X,Y) \ + Polygon.Length = NumPoints; Polygon.PointPtr = PointList; \ + FillConvexPolygon(&Polygon, Color, X, Y); +/* Describes a single 2D point */ +struct Point { + int X; /* X coordinate */ + int Y; /* Y coordinate */ +}; +/* Describes a single 3D point in homogeneous coordinates */ +struct Point3 { + double X; /* X coordinate */ + double Y; /* Y coordinate */ + double Z; /* Z coordinate */ + double W; +}; +/* Describes a series of points (used to store a list of vertices that + describe a polygon; each vertex is assumed to connect to the two adjacent + vertices, and the last vertex is assumed to connect to the first) */ + struct PointListHeader { + int Length; /* # of points */ + struct Point * PointPtr; /* pointer to list of points */ +}; +/* Describes beginning and ending X coordinates of a single horizontal line */ +struct HLine { + int XStart; /* X coordinate of leftmost pixel in line */ + int XEnd; /* X coordinate of rightmost pixel in line */ +}; +/* Describes a Length-long series of horizontal lines, all assumed to be on + contiguous scan lines starting at YStart and proceeding downward (describes + a scan-converted polygon to low-level hardware-dependent drawing code) */ +struct HLineList { + int Length; /* # of horizontal lines */ + int YStart; /* Y coordinate of topmost line */ + struct HLine * HLinePtr; /* pointer to list of horz lines */ +}; +struct Rect { int Left, Top, Right, Bottom; }; +/* Structure describing one face of an object (one polygon) */ +struct Face { + int * VertNums; /* pointer to vertex ptrs */ + int NumVerts; /* # of vertices */ + int Color; /* polygon color */ +}; +/* Structure describing an object */ +struct Object { + int NumVerts; + struct Point3 * VertexList; + struct Point3 * XformedVertexList; + struct Point3 * ProjectedVertexList; + struct Point * ScreenVertexList; + int NumFaces; + struct Face * FaceList; +}; +extern void XformVec(double Xform[4][4], double * SourceVec, double * DestVec); +extern void ConcatXforms(double SourceXform1[4][4], + double SourceXform2[4][4], double DestXform[4][4]); +extern void XformAndProjectPoly(double Xform[4][4], + struct Point3 * Poly, int PolyLength, int Color); +extern int FillConvexPolygon(struct PointListHeader *, int, int, int); +extern void Set320x240Mode(void); +extern void ShowPage(unsigned int StartOffset); +extern void FillRectangleX(int StartX, int StartY, int EndX, + int EndY, unsigned int PageBase, int Color); +extern void XformAndProjectPoints(double Xform[4][4],struct Object * ObjectToXform); +extern void DrawVisibleFaces(struct Object * ObjectToXform); +extern void AppendRotationX(double XformToChange[4][4], double Angle); +extern void AppendRotationY(double XformToChange[4][4], double Angle); +extern void AppendRotationZ(double XformToChange[4][4], double Angle); +extern int DisplayedPage, NonDisplayedPage; +extern struct Rect EraseRect[]; +``` ### A Note on Rounding Negative Numbers {#Heading6} diff --git a/52-02.md b/52-02.md index 96cb6f2..22e4d68 100644 --- a/52-02.md +++ b/52-02.md @@ -12,123 +12,127 @@ pages: 973-974 **LISTING 52.1 L52-1.C** - /* 3-D animation program to rotate 12 cubes. Uses fixed point. All C code - tested with Borland C++ in C compilation mode and the small model. */ +```c +/* 3-D animation program to rotate 12 cubes. Uses fixed point. All C code + tested with Borland C++ in C compilation mode and the small model. */ - #include - #include - #include "polygon.h" +#include +#include +#include "polygon.h" - /* base offset of page to which to draw */ - unsigned int CurrentPageBase = 0; - /* clip rectangle; clips to the screen */ - int ClipMinX = 0, ClipMinY = 0; - int ClipMaxX = SCREEN_WIDTH, ClipMaxY = SCREEN_HEIGHT; - static unsigned int PageStartOffsets[2] = - {PAGE0_START_OFFSET,PAGE1_START_OFFSET}; - int DisplayedPage, NonDisplayedPage; - int RecalcAllXforms = 1, NumObjects = 0; - Xform WorldViewXform; /* initialized from floats */ - /* pointers to objects */ - Object *ObjectList[MAX_OBJECTS]; +/* base offset of page to which to draw */ +unsigned int CurrentPageBase = 0; +/* clip rectangle; clips to the screen */ +int ClipMinX = 0, ClipMinY = 0; +int ClipMaxX = SCREEN_WIDTH, ClipMaxY = SCREEN_HEIGHT; +static unsigned int PageStartOffsets[2] = + {PAGE0_START_OFFSET,PAGE1_START_OFFSET}; +int DisplayedPage, NonDisplayedPage; +int RecalcAllXforms = 1, NumObjects = 0; +Xform WorldViewXform; /* initialized from floats */ +/* pointers to objects */ +Object *ObjectList[MAX_OBJECTS]; - void main() { - int Done = 0, i; - Object *ObjectPtr; - union REGS regset; +void main() { + int Done = 0, i; + Object *ObjectPtr; + union REGS regset; - InitializeFixedPoint(); /* set up fixed-point data */ - InitializeCubes(); /* set up cubes and add them to object list; other - objects would be initialized now, if there were any */ - Set320x240Mode(); /* set the screen to mode X */ - ShowPage(PageStartOffsets[DisplayedPage = 0]); - /* Keep transforming the cube, drawing it to the undisplayed page, - and flipping the page to show it */ - do { - /* For each object, regenerate viewing info, if necessary */ - for (i=0; iRecalcXform || - RecalcAllXforms) { - ObjectPtr->RecalcFunc(ObjectPtr); - ObjectPtr->RecalcXform = 0; - } - } - RecalcAllXforms = 0; - CurrentPageBase = /* select other page for drawing to */ - PageStartOffsets[NonDisplayedPage = DisplayedPage ^ 1]; - /* For each object, clear the portion of the non-displayed page - that was drawn to last time, then reset the erase extent */ - for (i=0; iEraseRect[NonDisplayedPage].Left, - ObjectPtr->EraseRect[NonDisplayedPage].Top, - ObjectPtr->EraseRect[NonDisplayedPage].Right, - ObjectPtr->EraseRect[NonDisplayedPage].Bottom, - CurrentPageBase, 0); - ObjectPtr->EraseRect[NonDisplayedPage].Left = - ObjectPtr->EraseRect[NonDisplayedPage].Top = 0x7FFF; - ObjectPtr->EraseRect[NonDisplayedPage].Right = - ObjectPtr->EraseRect[NonDisplayedPage].Bottom = 0; - } - /* Draw all objects */ - for (i=0; iDrawFunc(ObjectList[i]); - /* Flip to display the page into which we just drew */ - ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]); - /* Move and reorient each object */ - for (i=0; iMoveFunc(ObjectList[i]); - if (kbhit()) - if (getch() == 0x1B) Done = 1; /* Esc to exit */ - } while (!Done); - /* Return to text mode and exit */ - regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */ - int86(0x10, ®set, ®set); - exit(1); - } + InitializeFixedPoint(); /* set up fixed-point data */ + InitializeCubes(); /* set up cubes and add them to object list; other + objects would be initialized now, if there were any */ + Set320x240Mode(); /* set the screen to mode X */ + ShowPage(PageStartOffsets[DisplayedPage = 0]); + /* Keep transforming the cube, drawing it to the undisplayed page, + and flipping the page to show it */ + do { + /* For each object, regenerate viewing info, if necessary */ + for (i=0; iRecalcXform || + RecalcAllXforms) { + ObjectPtr->RecalcFunc(ObjectPtr); + ObjectPtr->RecalcXform = 0; + } + } + RecalcAllXforms = 0; + CurrentPageBase = /* select other page for drawing to */ + PageStartOffsets[NonDisplayedPage = DisplayedPage ^ 1]; + /* For each object, clear the portion of the non-displayed page + that was drawn to last time, then reset the erase extent */ + for (i=0; iEraseRect[NonDisplayedPage].Left, + ObjectPtr->EraseRect[NonDisplayedPage].Top, + ObjectPtr->EraseRect[NonDisplayedPage].Right, + ObjectPtr->EraseRect[NonDisplayedPage].Bottom, + CurrentPageBase, 0); + ObjectPtr->EraseRect[NonDisplayedPage].Left = + ObjectPtr->EraseRect[NonDisplayedPage].Top = 0x7FFF; + ObjectPtr->EraseRect[NonDisplayedPage].Right = + ObjectPtr->EraseRect[NonDisplayedPage].Bottom = 0; + } + /* Draw all objects */ + for (i=0; iDrawFunc(ObjectList[i]); + /* Flip to display the page into which we just drew */ + ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]); + /* Move and reorient each object */ + for (i=0; iMoveFunc(ObjectList[i]); + if (kbhit()) + if (getch() == 0x1B) Done = 1; /* Esc to exit */ + } while (!Done); + /* Return to text mode and exit */ + regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */ + int86(0x10, ®set, ®set); + exit(1); +} +``` **LISTING 52.2 L52-2.C** - /* Transforms all vertices in the specified polygon-based object into view - space, then perspective projects them to screen space and maps them to screen - coordinates, storing results in the object. Recalculates object->view - transformation because only if transform changes would we bother - to retransform the vertices. */ +```c +/* Transforms all vertices in the specified polygon-based object into view + space, then perspective projects them to screen space and maps them to screen + coordinates, storing results in the object. Recalculates object->view + transformation because only if transform changes would we bother + to retransform the vertices. */ - #include - #include "polygon.h" +#include +#include "polygon.h" - void XformAndProjectPObject(PObject * ObjectToXform) - { - int i, NumPoints = ObjectToXform->NumVerts; - Point3 * Points = ObjectToXform->VertexList; - Point3 * XformedPoints = ObjectToXform->XformedVertexList; - Point3 * ProjectedPoints = ObjectToXform->ProjectedVertexList; - Point * ScreenPoints = ObjectToXform->ScreenVertexList; +void XformAndProjectPObject(PObject * ObjectToXform) +{ + int i, NumPoints = ObjectToXform->NumVerts; + Point3 * Points = ObjectToXform->VertexList; + Point3 * XformedPoints = ObjectToXform->XformedVertexList; + Point3 * ProjectedPoints = ObjectToXform->ProjectedVertexList; + Point * ScreenPoints = ObjectToXform->ScreenVertexList; - /* Recalculate the object->view transform */ - ConcatXforms(WorldViewXform, ObjectToXform->XformToWorld, - ObjectToXform->XformToView); - /* Apply that new transformation and project the points */ - for (i=0; iXformToView, (Fixedpoint *) Points, - (Fixedpoint *) XformedPoints); - /* Perspective-project to screen space */ - ProjectedPoints->X = - FixedMul(FixedDiv(XformedPoints->X, XformedPoints->Z), - DOUBLE_TO_FIXED(PROJECTION_RATIO * (SCREEN_WIDTH/2))); - ProjectedPoints->Y = - FixedMul(FixedDiv(XformedPoints->Y, XformedPoints->Z), - DOUBLE_TO_FIXED(PROJECTION_RATIO * (SCREEN_WIDTH/2))); - ProjectedPoints->Z = XformedPoints->Z; - /* Convert to screen coordinates. The Y coord is negated to flip from - increasing Y being up to increasing Y being down, as expected by polygon - filler. Add in half the screen width and height to center on screen. */ - ScreenPoints->X = ((int) ((ProjectedPoints->X + - DOUBLE_TO_FIXED(0.5)) >> 16)) + SCREEN_WIDTH/2; - ScreenPoints->Y = (-((int) ((ProjectedPoints->Y + - DOUBLE_TO_FIXED(0.5)) >> 16))) + SCREEN_HEIGHT/2; - } - } + /* Recalculate the object->view transform */ + ConcatXforms(WorldViewXform, ObjectToXform->XformToWorld, + ObjectToXform->XformToView); + /* Apply that new transformation and project the points */ + for (i=0; iXformToView, (Fixedpoint *) Points, + (Fixedpoint *) XformedPoints); + /* Perspective-project to screen space */ + ProjectedPoints->X = + FixedMul(FixedDiv(XformedPoints->X, XformedPoints->Z), + DOUBLE_TO_FIXED(PROJECTION_RATIO * (SCREEN_WIDTH/2))); + ProjectedPoints->Y = + FixedMul(FixedDiv(XformedPoints->Y, XformedPoints->Z), + DOUBLE_TO_FIXED(PROJECTION_RATIO * (SCREEN_WIDTH/2))); + ProjectedPoints->Z = XformedPoints->Z; + /* Convert to screen coordinates. The Y coord is negated to flip from + increasing Y being up to increasing Y being down, as expected by polygon + filler. Add in half the screen width and height to center on screen. */ + ScreenPoints->X = ((int) ((ProjectedPoints->X + + DOUBLE_TO_FIXED(0.5)) >> 16)) + SCREEN_WIDTH/2; + ScreenPoints->Y = (-((int) ((ProjectedPoints->Y + + DOUBLE_TO_FIXED(0.5)) >> 16))) + SCREEN_HEIGHT/2; + } +} +``` diff --git a/52-03.md b/52-03.md index a2a167f..4cf9bf2 100644 --- a/52-03.md +++ b/52-03.md @@ -12,158 +12,164 @@ pages: 975-977 **LISTING 52.3 L52-3.C** - /* Routines to perform incremental rotations around the three axes. */ +```c +/* Routines to perform incremental rotations around the three axes. */ - #include - #include "polygon.h" +#include +#include "polygon.h" - /* Concatenate a rotation by Angle around the X axis to transformation in - XformToChange, placing the result back into XformToChange. */ - void AppendRotationX(Xform XformToChange, double Angle) - { - Fixedpoint Temp10, Temp11, Temp12, Temp20, Temp21, Temp22; - Fixedpoint CosTemp = DOUBLE_TO_FIXED(cos(Angle)); - Fixedpoint SinTemp = DOUBLE_TO_FIXED(sin(Angle)); +/* Concatenate a rotation by Angle around the X axis to transformation in + XformToChange, placing the result back into XformToChange. */ +void AppendRotationX(Xform XformToChange, double Angle) +{ + Fixedpoint Temp10, Temp11, Temp12, Temp20, Temp21, Temp22; + Fixedpoint CosTemp = DOUBLE_TO_FIXED(cos(Angle)); + Fixedpoint SinTemp = DOUBLE_TO_FIXED(sin(Angle)); - /* Calculate the new values of the six affected matrix entries */ - Temp10 = FixedMul(CosTemp, XformToChange[1][0]) + - FixedMul(-SinTemp, XformToChange[2][0]); - Temp11 = FixedMul(CosTemp, XformToChange[1][1]) + - FixedMul(-SinTemp, XformToChange[2][1]); - Temp12 = FixedMul(CosTemp, XformToChange[1][2]) + - FixedMul(-SinTemp, XformToChange[2][2]); - Temp20 = FixedMul(SinTemp, XformToChange[1][0]) + - FixedMul(CosTemp, XformToChange[2][0]); - Temp21 = FixedMul(SinTemp, XformToChange[1][1]) + - FixedMul(CosTemp, XformToChange[2][1]); - Temp22 = FixedMul(SinTemp, XformToChange[1][2]) + - FixedMul(CosTemp, XformToChange[2][2]); - /* Put the results back into XformToChange */ - XformToChange[1][0] = Temp10; XformToChange[1][1] = Temp11; - XformToChange[1][2] = Temp12; XformToChange[2][0] = Temp20; - XformToChange[2][1] = Temp21; XformToChange[2][2] = Temp22; - } - /* Concatenate a rotation by Angle around the Y axis to transformation in - XformToChange, placing the result back into XformToChange. */ - void AppendRotationY(Xform XformToChange, double Angle) - { - Fixedpoint Temp00, Temp01, Temp02, Temp20, Temp21, Temp22; - Fixedpoint CosTemp = DOUBLE_TO_FIXED(cos(Angle)); - Fixedpoint SinTemp = DOUBLE_TO_FIXED(sin(Angle)); + /* Calculate the new values of the six affected matrix entries */ + Temp10 = FixedMul(CosTemp, XformToChange[1][0]) + + FixedMul(-SinTemp, XformToChange[2][0]); + Temp11 = FixedMul(CosTemp, XformToChange[1][1]) + + FixedMul(-SinTemp, XformToChange[2][1]); + Temp12 = FixedMul(CosTemp, XformToChange[1][2]) + + FixedMul(-SinTemp, XformToChange[2][2]); + Temp20 = FixedMul(SinTemp, XformToChange[1][0]) + + FixedMul(CosTemp, XformToChange[2][0]); + Temp21 = FixedMul(SinTemp, XformToChange[1][1]) + + FixedMul(CosTemp, XformToChange[2][1]); + Temp22 = FixedMul(SinTemp, XformToChange[1][2]) + + FixedMul(CosTemp, XformToChange[2][2]); + /* Put the results back into XformToChange */ + XformToChange[1][0] = Temp10; XformToChange[1][1] = Temp11; + XformToChange[1][2] = Temp12; XformToChange[2][0] = Temp20; + XformToChange[2][1] = Temp21; XformToChange[2][2] = Temp22; +} +/* Concatenate a rotation by Angle around the Y axis to transformation in + XformToChange, placing the result back into XformToChange. */ +void AppendRotationY(Xform XformToChange, double Angle) +{ + Fixedpoint Temp00, Temp01, Temp02, Temp20, Temp21, Temp22; + Fixedpoint CosTemp = DOUBLE_TO_FIXED(cos(Angle)); + Fixedpoint SinTemp = DOUBLE_TO_FIXED(sin(Angle)); - /* Calculate the new values of the six affected matrix entries */ - Temp00 = FixedMul(CosTemp, XformToChange[0][0]) + - FixedMul(SinTemp, XformToChange[2][0]); - Temp01 = FixedMul(CosTemp, XformToChange[0][1]) + - FixedMul(SinTemp, XformToChange[2][1]); - Temp02 = FixedMul(CosTemp, XformToChange[0][2]) + - FixedMul(SinTemp, XformToChange[2][2]); - Temp20 = FixedMul(-SinTemp, XformToChange[0][0]) + - FixedMul( CosTemp, XformToChange[2][0]); - Temp21 = FixedMul(-SinTemp, XformToChange[0][1]) + - FixedMul(CosTemp, XformToChange[2][1]); - Temp22 = FixedMul(-SinTemp, XformToChange[0][2]) + - FixedMul(CosTemp, XformToChange[2][2]); - /* Put the results back into XformToChange */ - XformToChange[0][0] = Temp00; XformToChange[0][1] = Temp01; - XformToChange[0][2] = Temp02; XformToChange[2][0] = Temp20; - XformToChange[2][1] = Temp21; XformToChange[2][2] = Temp22; - } + /* Calculate the new values of the six affected matrix entries */ + Temp00 = FixedMul(CosTemp, XformToChange[0][0]) + + FixedMul(SinTemp, XformToChange[2][0]); + Temp01 = FixedMul(CosTemp, XformToChange[0][1]) + + FixedMul(SinTemp, XformToChange[2][1]); + Temp02 = FixedMul(CosTemp, XformToChange[0][2]) + + FixedMul(SinTemp, XformToChange[2][2]); + Temp20 = FixedMul(-SinTemp, XformToChange[0][0]) + + FixedMul( CosTemp, XformToChange[2][0]); + Temp21 = FixedMul(-SinTemp, XformToChange[0][1]) + + FixedMul(CosTemp, XformToChange[2][1]); + Temp22 = FixedMul(-SinTemp, XformToChange[0][2]) + + FixedMul(CosTemp, XformToChange[2][2]); + /* Put the results back into XformToChange */ + XformToChange[0][0] = Temp00; XformToChange[0][1] = Temp01; + XformToChange[0][2] = Temp02; XformToChange[2][0] = Temp20; + XformToChange[2][1] = Temp21; XformToChange[2][2] = Temp22; +} - /* Concatenate a rotation by Angle around the Z axis to transformation in - XformToChange, placing the result back into XformToChange. */ - void AppendRotationZ(Xform XformToChange, double Angle) - { - Fixedpoint Temp00, Temp01, Temp02, Temp10, Temp11, Temp12; - Fixedpoint CosTemp = DOUBLE_TO_FIXED(cos(Angle)); - Fixedpoint SinTemp = DOUBLE_TO_FIXED(sin(Angle)); +/* Concatenate a rotation by Angle around the Z axis to transformation in + XformToChange, placing the result back into XformToChange. */ +void AppendRotationZ(Xform XformToChange, double Angle) +{ + Fixedpoint Temp00, Temp01, Temp02, Temp10, Temp11, Temp12; + Fixedpoint CosTemp = DOUBLE_TO_FIXED(cos(Angle)); + Fixedpoint SinTemp = DOUBLE_TO_FIXED(sin(Angle)); - /* Calculate the new values of the six affected matrix entries */ - Temp00 = FixedMul(CosTemp, XformToChange[0][0]) + - FixedMul(-SinTemp, XformToChange[1][0]); - Temp01 = FixedMul(CosTemp, XformToChange[0][1]) + - FixedMul(-SinTemp, XformToChange[1][1]); - Temp02 = FixedMul(CosTemp, XformToChange[0][2]) + - FixedMul(-SinTemp, XformToChange[1][2]); - Temp10 = FixedMul(SinTemp, XformToChange[0][0]) + - FixedMul(CosTemp, XformToChange[1][0]); - Temp11 = FixedMul(SinTemp, XformToChange[0][1]) + - FixedMul(CosTemp, XformToChange[1][1]); - Temp12 = FixedMul(SinTemp, XformToChange[0][2]) + - FixedMul(CosTemp, XformToChange[1][2]); - /* Put the results back into XformToChange */ - XformToChange[0][0] = Temp00; XformToChange[0][1] = Temp01; - XformToChange[0][2] = Temp02; XformToChange[1][0] = Temp10; - XformToChange[1][1] = Temp11; XformToChange[1][2] = Temp12; - } + /* Calculate the new values of the six affected matrix entries */ + Temp00 = FixedMul(CosTemp, XformToChange[0][0]) + + FixedMul(-SinTemp, XformToChange[1][0]); + Temp01 = FixedMul(CosTemp, XformToChange[0][1]) + + FixedMul(-SinTemp, XformToChange[1][1]); + Temp02 = FixedMul(CosTemp, XformToChange[0][2]) + + FixedMul(-SinTemp, XformToChange[1][2]); + Temp10 = FixedMul(SinTemp, XformToChange[0][0]) + + FixedMul(CosTemp, XformToChange[1][0]); + Temp11 = FixedMul(SinTemp, XformToChange[0][1]) + + FixedMul(CosTemp, XformToChange[1][1]); + Temp12 = FixedMul(SinTemp, XformToChange[0][2]) + + FixedMul(CosTemp, XformToChange[1][2]); + /* Put the results back into XformToChange */ + XformToChange[0][0] = Temp00; XformToChange[0][1] = Temp01; + XformToChange[0][2] = Temp02; XformToChange[1][0] = Temp10; + XformToChange[1][1] = Temp11; XformToChange[1][2] = Temp12; +} +``` **LISTING 52.4 L52-4.C** - /* Fixed point matrix arithmetic functions. */ +```c +/* 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 - by assuming the W coord is 1 and bottom row of matrix is 0 0 0 1, and doesn't - bother to set the W coordinate of the destination. */ - void XformVec(Xform WorkingXform, Fixedpoint *SourceVec, - Fixedpoint *DestVec) - { - int i; +/* 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 bottom row of matrix is 0 0 0 1, and doesn't + bother to set the W coordinate of the destination. */ +void XformVec(Xform WorkingXform, Fixedpoint *SourceVec, + Fixedpoint *DestVec) +{ + int i; - for (i=0; i<3; i++) - DestVec[i] = FixedMul(WorkingXform[i][0], SourceVec[0]) + - FixedMul(WorkingXform[i][1], SourceVec[1]) + - FixedMul(WorkingXform[i][2], SourceVec[2]) + - WorkingXform[i][3]; /* no need to multiply by W = 1 */ - } + for (i=0; i<3; i++) + DestVec[i] = FixedMul(WorkingXform[i][0], SourceVec[0]) + + FixedMul(WorkingXform[i][1], SourceVec[1]) + + FixedMul(WorkingXform[i][2], SourceVec[2]) + + WorkingXform[i][3]; /* no need to multiply by W = 1 */ +} - /* Matrix multiplies SourceXform1 by SourceXform2 and stores result in - DestXform. Multiplies a 4x4 matrix times a 4x4 matrix; result is a 4x4 matrix. - Cheats by assuming bottom row of each matrix is 0 0 0 1, and doesn't bother - to set the bottom row of the destination. */ - void ConcatXforms(Xform SourceXform1, Xform SourceXform2, - Xform DestXform) - { - int i, j; +/* Matrix multiplies SourceXform1 by SourceXform2 and stores result in + DestXform. Multiplies a 4x4 matrix times a 4x4 matrix; result is a 4x4 matrix. + Cheats by assuming bottom row of each matrix is 0 0 0 1, and doesn't bother + to set the bottom row of the destination. */ +void ConcatXforms(Xform SourceXform1, Xform SourceXform2, + Xform DestXform) +{ + int i, j; - for (i=0; i<3; i++) { - for (j=0; j<4; j++) - DestXform[i][j] = - FixedMul(SourceXform1[i][0], SourceXform2[0][j]) + - FixedMul(SourceXform1[i][1], SourceXform2[1][j]) + - FixedMul(SourceXform1[i][2], SourceXform2[2][j]) + - SourceXform1[i][3]; - } - } + for (i=0; i<3; i++) { + for (j=0; j<4; j++) + DestXform[i][j] = + FixedMul(SourceXform1[i][0], SourceXform2[0][j]) + + FixedMul(SourceXform1[i][1], SourceXform2[1][j]) + + FixedMul(SourceXform1[i][2], SourceXform2[2][j]) + + SourceXform1[i][3]; + } +} +``` **LISTING 52.5 L52-5.C** - /* Set up basic data that needs to be in fixed point, to avoid data - definition hassles. */ +```c +/* 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] = { - {15,15,15},{15,15,-15},{15,-15,15},{15,-15,-15}, - {-15,15,15},{-15,15,-15},{-15,-15,15},{-15,-15,-15} }; - /* Transformation from world space into view space (no transformation, - currently) */ - static int IntWorldViewXform[3][4] = { - {1,0,0,0}, {0,1,0,0}, {0,0,1,0}}; +/* All vertices in the basic cube */ +static IntPoint3 IntCubeVerts[NUM_CUBE_VERTS] = { + {15,15,15},{15,15,-15},{15,-15,15},{15,-15,-15}, + {-15,15,15},{-15,15,-15},{-15,-15,15},{-15,-15,-15} }; +/* Transformation from world space into view space (no transformation, + currently) */ +static int IntWorldViewXform[3][4] = { + {1,0,0,0}, {0,1,0,0}, {0,0,1,0}}; - void InitializeFixedPoint() - { - int i, j; +void InitializeFixedPoint() +{ + int i, j; - for (i=0; i<3; i++) - for (j=0; j<4; j++) - WorldViewXform[i][j] = INT_TO_FIXED(IntWorldViewXform[i][j]); - for (i=0; iRDelayCount == 0) { /* rotate */ - ObjectToMove->RDelayCount = ObjectToMove->RDelayCountBase; - if (ObjectToMove->Rotate.RotateX != 0.0) - AppendRotationX(ObjectToMove->XformToWorld, - ObjectToMove->Rotate.RotateX); - if (ObjectToMove->Rotate.RotateY != 0.0) - AppendRotationY(ObjectToMove->XformToWorld, - ObjectToMove->Rotate.RotateY); - if (ObjectToMove->Rotate.RotateZ != 0.0) - AppendRotationZ(ObjectToMove->XformToWorld, - ObjectToMove->Rotate.RotateZ); - ObjectToMove->RecalcXform = 1; - } - /* Move in Z, checking for bouncing and stopping */ - if (--ObjectToMove->MDelayCount == 0) { - ObjectToMove->MDelayCount = ObjectToMove->MDelayCountBase; - ObjectToMove->XformToWorld[2][3] += ObjectToMove->Move.MoveZ; - if (ObjectToMove->XformToWorld[2][3]>ObjectToMove->Move.MaxZ) - ObjectToMove->Move.MoveZ = 0; /* stop if close enough */ - ObjectToMove->RecalcXform = 1; - } - } +void RotateAndMovePObject(PObject * ObjectToMove) +{ + if (--ObjectToMove->RDelayCount == 0) { /* rotate */ + ObjectToMove->RDelayCount = ObjectToMove->RDelayCountBase; + if (ObjectToMove->Rotate.RotateX != 0.0) + AppendRotationX(ObjectToMove->XformToWorld, + ObjectToMove->Rotate.RotateX); + if (ObjectToMove->Rotate.RotateY != 0.0) + AppendRotationY(ObjectToMove->XformToWorld, + ObjectToMove->Rotate.RotateY); + if (ObjectToMove->Rotate.RotateZ != 0.0) + AppendRotationZ(ObjectToMove->XformToWorld, + ObjectToMove->Rotate.RotateZ); + ObjectToMove->RecalcXform = 1; + } + /* Move in Z, checking for bouncing and stopping */ + if (--ObjectToMove->MDelayCount == 0) { + ObjectToMove->MDelayCount = ObjectToMove->MDelayCountBase; + ObjectToMove->XformToWorld[2][3] += ObjectToMove->Move.MoveZ; + if (ObjectToMove->XformToWorld[2][3]>ObjectToMove->Move.MaxZ) + ObjectToMove->Move.MoveZ = 0; /* stop if close enough */ + ObjectToMove->RecalcXform = 1; + } +} +``` **LISTING 52.7 L52-7.C** - /* Draws all visible faces in specified polygon-based object. Object must have - previously been transformed and projected, so that ScreenVertexList array is - filled in. */ +```c +/* Draws all visible faces in specified polygon-based object. Object must have + previously been transformed and projected, so that ScreenVertexList array is + filled in. */ - #include "polygon.h" +#include "polygon.h" - void DrawPObject(PObject * ObjectToXform) - { - int i, j, NumFaces = ObjectToXform->NumFaces, NumVertices; - int * VertNumsPtr; - Face * FacePtr = ObjectToXform->FaceList; - Point * ScreenPoints = ObjectToXform->ScreenVertexList; - long v1, v2, w1, w2; - Point Vertices[MAX_POLY_LENGTH]; - PointListHeader Polygon; +void DrawPObject(PObject * ObjectToXform) +{ + int i, j, NumFaces = ObjectToXform->NumFaces, NumVertices; + int * VertNumsPtr; + Face * FacePtr = ObjectToXform->FaceList; + Point * ScreenPoints = ObjectToXform->ScreenVertexList; + long v1, v2, w1, w2; + Point Vertices[MAX_POLY_LENGTH]; + PointListHeader Polygon; - /* Draw each visible face (polygon) of the object in turn */ - for (i=0; iNumVerts; - /* Copy over the face's vertices from the vertex list */ - for (j=0, VertNumsPtr=FacePtr->VertNums; j 0) { - /* It is facing the screen, so draw */ - /* Appropriately adjust the extent of the rectangle used to - erase this object later */ - for (j=0; j - ObjectToXform->EraseRect[NonDisplayedPage].Right) - if (Vertices[j].X < SCREEN_WIDTH) - ObjectToXform->EraseRect[NonDisplayedPage].Right = - Vertices[j].X; - else ObjectToXform->EraseRect[NonDisplayedPage].Right = - SCREEN_WIDTH; - if (Vertices[j].Y > - ObjectToXform->EraseRect[NonDisplayedPage].Bottom) - if (Vertices[j].Y < SCREEN_HEIGHT) - ObjectToXform->EraseRect[NonDisplayedPage].Bottom = - Vertices[j].Y; - else ObjectToXform->EraseRect[NonDisplayedPage].Bottom= - SCREEN_HEIGHT; - if (Vertices[j].X < - ObjectToXform->EraseRect[NonDisplayedPage].Left) - if (Vertices[j].X > 0) - ObjectToXform->EraseRect[NonDisplayedPage].Left = - Vertices[j].X; - else ObjectToXform->EraseRect[NonDisplayedPage].Left=0; - if (Vertices[j].Y < - ObjectToXform->EraseRect[NonDisplayedPage].Top) - if (Vertices[j].Y > 0) - ObjectToXform->EraseRect[NonDisplayedPage].Top = - Vertices[j].Y; - else ObjectToXform->EraseRect[NonDisplayedPage].Top=0; - } - /* Draw the polygon */ - DRAW_POLYGON(Vertices, NumVertices, FacePtr->Color, 0, 0); - } - } - } + /* Draw each visible face (polygon) of the object in turn */ + for (i=0; iNumVerts; + /* Copy over the face's vertices from the vertex list */ + for (j=0, VertNumsPtr=FacePtr->VertNums; j 0) { + /* It is facing the screen, so draw */ + /* Appropriately adjust the extent of the rectangle used to + erase this object later */ + for (j=0; j + ObjectToXform->EraseRect[NonDisplayedPage].Right) + if (Vertices[j].X < SCREEN_WIDTH) + ObjectToXform->EraseRect[NonDisplayedPage].Right = + Vertices[j].X; + else ObjectToXform->EraseRect[NonDisplayedPage].Right = + SCREEN_WIDTH; + if (Vertices[j].Y > + ObjectToXform->EraseRect[NonDisplayedPage].Bottom) + if (Vertices[j].Y < SCREEN_HEIGHT) + ObjectToXform->EraseRect[NonDisplayedPage].Bottom = + Vertices[j].Y; + else ObjectToXform->EraseRect[NonDisplayedPage].Bottom= + SCREEN_HEIGHT; + if (Vertices[j].X < + ObjectToXform->EraseRect[NonDisplayedPage].Left) + if (Vertices[j].X > 0) + ObjectToXform->EraseRect[NonDisplayedPage].Left = + Vertices[j].X; + else ObjectToXform->EraseRect[NonDisplayedPage].Left=0; + if (Vertices[j].Y < + ObjectToXform->EraseRect[NonDisplayedPage].Top) + if (Vertices[j].Y > 0) + ObjectToXform->EraseRect[NonDisplayedPage].Top = + Vertices[j].Y; + else ObjectToXform->EraseRect[NonDisplayedPage].Top=0; + } + /* Draw the polygon */ + DRAW_POLYGON(Vertices, NumVertices, FacePtr->Color, 0, 0); + } + } +} +``` diff --git a/52-05.md b/52-05.md index 343009f..e6cf069 100644 --- a/52-05.md +++ b/52-05.md @@ -12,125 +12,127 @@ pages: 979-981 **LISTING 52.8 L52-8.C** - /* Initializes the cubes and adds them to the object list. */ +```c +/* Initializes the cubes and adds them to the object list. */ - #include - #include - #include "polygon.h" +#include +#include +#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 */ - #define ROT_2 (M_PI / 90.0) /* rotate 2 degrees at a time */ - #define NUM_CUBES 12 /* # of cubes */ +#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 */ +#define ROT_2 (M_PI / 90.0) /* rotate 2 degrees at a time */ +#define NUM_CUBES 12 /* # of cubes */ - Point3 CubeVerts[NUM_CUBE_VERTS]; /* set elsewhere, from floats */ - /* vertex indices for individual cube faces */ - static int Face1[] = {1,3,2,0}; - static int Face2[] = {5,7,3,1}; - static int Face3[] = {4,5,1,0}; - static int Face4[] = {3,7,6,2}; - static int Face5[] = {5,4,6,7}; - static int Face6[] = {0,2,6,4}; - static int *VertNumList[]={Face1, Face2, Face3, Face4, Face5, Face6}; - static int VertsInFace[]={ sizeof(Face1)/sizeof(int), - sizeof(Face2)/sizeof(int), sizeof(Face3)/sizeof(int), - sizeof(Face4)/sizeof(int), sizeof(Face5)/sizeof(int), - sizeof(Face6)/sizeof(int) }; - /* X, Y, Z rotations for cubes */ - static RotateControl InitialRotate[NUM_CUBES] = { - {0.0,ROT_6,ROT_6},{ROT_3,0.0,ROT_3},{ROT_3,ROT_3,0.0}, - {ROT_3,-ROT_3,0.0},{-ROT_3,ROT_2,0.0},{-ROT_6,-ROT_3,0.0}, - {ROT_3,0.0,-ROT_6},{-ROT_2,0.0,ROT_3},{-ROT_3,0.0,-ROT_3}, - {0.0,ROT_2,-ROT_2},{0.0,-ROT_3,ROT_3},{0.0,-ROT_6,-ROT_6},}; - static MoveControl InitialMove[NUM_CUBES] = { - {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, - {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, - {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, - {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, - {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, - {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, }; - /* face colors for various cubes */ - static int Colors[NUM_CUBES][NUM_CUBE_FACES] = { - {15,14,12,11,10,9},{1,2,3,4,5,6},{35,37,39,41,43,45}, - {47,49,51,53,55,57},{59,61,63,65,67,69},{71,73,75,77,79,81}, - {83,85,87,89,91,93},{95,97,99,101,103,105}, - {107,109,111,113,115,117},{119,121,123,125,127,129}, - {131,133,135,137,139,141},{143,145,147,149,151,153} }; - /* starting coordinates for cubes in world space */ - static int CubeStartCoords[NUM_CUBES][3] = { - {100,0,-6000}, {100,70,-6000}, {100,-70,-6000}, {33,0,-6000}, - {33,70,-6000}, {33,-70,-6000}, {-33,0,-6000}, {-33,70,-6000}, - {-33,-70,-6000},{-100,0,-6000}, {-100,70,-6000}, {-100,-70,-6000}}; - /* delay counts (speed control) for cubes */ - static int InitRDelayCounts[NUM_CUBES] = {1,2,1,2,1,1,1,1,1,2,1,1}; - static int BaseRDelayCounts[NUM_CUBES] = {1,2,1,2,2,1,1,1,2,2,2,1}; - static int InitMDelayCounts[NUM_CUBES] = {1,1,1,1,1,1,1,1,1,1,1,1}; - static int BaseMDelayCounts[NUM_CUBES] = {1,1,1,1,1,1,1,1,1,1,1,1}; +Point3 CubeVerts[NUM_CUBE_VERTS]; /* set elsewhere, from floats */ +/* vertex indices for individual cube faces */ +static int Face1[] = {1,3,2,0}; +static int Face2[] = {5,7,3,1}; +static int Face3[] = {4,5,1,0}; +static int Face4[] = {3,7,6,2}; +static int Face5[] = {5,4,6,7}; +static int Face6[] = {0,2,6,4}; +static int *VertNumList[]={Face1, Face2, Face3, Face4, Face5, Face6}; +static int VertsInFace[]={ sizeof(Face1)/sizeof(int), + sizeof(Face2)/sizeof(int), sizeof(Face3)/sizeof(int), + sizeof(Face4)/sizeof(int), sizeof(Face5)/sizeof(int), + sizeof(Face6)/sizeof(int) }; +/* X, Y, Z rotations for cubes */ +static RotateControl InitialRotate[NUM_CUBES] = { + {0.0,ROT_6,ROT_6},{ROT_3,0.0,ROT_3},{ROT_3,ROT_3,0.0}, + {ROT_3,-ROT_3,0.0},{-ROT_3,ROT_2,0.0},{-ROT_6,-ROT_3,0.0}, + {ROT_3,0.0,-ROT_6},{-ROT_2,0.0,ROT_3},{-ROT_3,0.0,-ROT_3}, + {0.0,ROT_2,-ROT_2},{0.0,-ROT_3,ROT_3},{0.0,-ROT_6,-ROT_6},}; +static MoveControl InitialMove[NUM_CUBES] = { + {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, + {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, + {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, + {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, + {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, + {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, }; +/* face colors for various cubes */ +static int Colors[NUM_CUBES][NUM_CUBE_FACES] = { + {15,14,12,11,10,9},{1,2,3,4,5,6},{35,37,39,41,43,45}, + {47,49,51,53,55,57},{59,61,63,65,67,69},{71,73,75,77,79,81}, + {83,85,87,89,91,93},{95,97,99,101,103,105}, + {107,109,111,113,115,117},{119,121,123,125,127,129}, + {131,133,135,137,139,141},{143,145,147,149,151,153} }; +/* starting coordinates for cubes in world space */ +static int CubeStartCoords[NUM_CUBES][3] = { + {100,0,-6000}, {100,70,-6000}, {100,-70,-6000}, {33,0,-6000}, + {33,70,-6000}, {33,-70,-6000}, {-33,0,-6000}, {-33,70,-6000}, + {-33,-70,-6000},{-100,0,-6000}, {-100,70,-6000}, {-100,-70,-6000}}; +/* delay counts (speed control) for cubes */ +static int InitRDelayCounts[NUM_CUBES] = {1,2,1,2,1,1,1,1,1,2,1,1}; +static int BaseRDelayCounts[NUM_CUBES] = {1,2,1,2,2,1,1,1,2,2,2,1}; +static int InitMDelayCounts[NUM_CUBES] = {1,1,1,1,1,1,1,1,1,1,1,1}; +static int BaseMDelayCounts[NUM_CUBES] = {1,1,1,1,1,1,1,1,1,1,1,1}; - void InitializeCubes() - { - int i, j, k; - PObject *WorkingCube; +void InitializeCubes() +{ + int i, j, k; + PObject *WorkingCube; - for (i=0; iDrawFunc = DrawPObject; - WorkingCube->RecalcFunc = XformAndProjectPObject; - WorkingCube->MoveFunc = RotateAndMovePObject; - WorkingCube->RecalcXform = 1; - for (k=0; k<2; k++) { - WorkingCube->EraseRect[k].Left = - WorkingCube->EraseRect[k].Top = 0x7FFF; - WorkingCube->EraseRect[k].Right = 0; - WorkingCube->EraseRect[k].Bottom = 0; - } - WorkingCube->RDelayCount = InitRDelayCounts[i]; - WorkingCube->RDelayCountBase = BaseRDelayCounts[i]; - WorkingCube->MDelayCount = InitMDelayCounts[i]; - WorkingCube->MDelayCountBase = BaseMDelayCounts[i]; - /* Set the object->world xform to none */ - for (j=0; j<3; j++) - for (k=0; k<4; k++) - WorkingCube->XformToWorld[j][k] = INT_TO_FIXED(0); - WorkingCube->XformToWorld[0][0] = - WorkingCube->XformToWorld[1][1] = - WorkingCube->XformToWorld[2][2] = - WorkingCube->XformToWorld[3][3] = INT_TO_FIXED(1); - /* Set the initial location */ - for (j=0; j<3; j++) WorkingCube->XformToWorld[j][3] = - INT_TO_FIXED(CubeStartCoords[i][j]); - WorkingCube->NumVerts = NUM_CUBE_VERTS; - WorkingCube->VertexList = CubeVerts; - WorkingCube->NumFaces = NUM_CUBE_FACES; - WorkingCube->Rotate = InitialRotate[i]; - WorkingCube->Move.MoveX = INT_TO_FIXED(InitialMove[i].MoveX); - WorkingCube->Move.MoveY = INT_TO_FIXED(InitialMove[i].MoveY); - WorkingCube->Move.MoveZ = INT_TO_FIXED(InitialMove[i].MoveZ); - WorkingCube->Move.MinX = INT_TO_FIXED(InitialMove[i].MinX); - WorkingCube->Move.MinY = INT_TO_FIXED(InitialMove[i].MinY); - WorkingCube->Move.MinZ = INT_TO_FIXED(InitialMove[i].MinZ); - WorkingCube->Move.MaxX = INT_TO_FIXED(InitialMove[i].MaxX); - WorkingCube->Move.MaxY = INT_TO_FIXED(InitialMove[i].MaxY); - 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); } - if ((WorkingCube->ProjectedVertexList = - malloc(NUM_CUBE_VERTS*sizeof(Point3))) == NULL) { - 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); } - if ((WorkingCube->FaceList = - malloc(NUM_CUBE_FACES*sizeof(Face))) == NULL) { - printf("Couldn't get memory\n"); exit(1); } - /* Initialize the faces */ - for (j=0; jFaceList[j].VertNums = VertNumList[j]; - WorkingCube->FaceList[j].NumVerts = VertsInFace[j]; - WorkingCube->FaceList[j].Color = Colors[i][j]; - } - ObjectList[NumObjects++] = (Object *)WorkingCube; - } - } + for (i=0; iDrawFunc = DrawPObject; + WorkingCube->RecalcFunc = XformAndProjectPObject; + WorkingCube->MoveFunc = RotateAndMovePObject; + WorkingCube->RecalcXform = 1; + for (k=0; k<2; k++) { + WorkingCube->EraseRect[k].Left = + WorkingCube->EraseRect[k].Top = 0x7FFF; + WorkingCube->EraseRect[k].Right = 0; + WorkingCube->EraseRect[k].Bottom = 0; + } + WorkingCube->RDelayCount = InitRDelayCounts[i]; + WorkingCube->RDelayCountBase = BaseRDelayCounts[i]; + WorkingCube->MDelayCount = InitMDelayCounts[i]; + WorkingCube->MDelayCountBase = BaseMDelayCounts[i]; + /* Set the object->world xform to none */ + for (j=0; j<3; j++) + for (k=0; k<4; k++) + WorkingCube->XformToWorld[j][k] = INT_TO_FIXED(0); + WorkingCube->XformToWorld[0][0] = + WorkingCube->XformToWorld[1][1] = + WorkingCube->XformToWorld[2][2] = + WorkingCube->XformToWorld[3][3] = INT_TO_FIXED(1); + /* Set the initial location */ + for (j=0; j<3; j++) WorkingCube->XformToWorld[j][3] = + INT_TO_FIXED(CubeStartCoords[i][j]); + WorkingCube->NumVerts = NUM_CUBE_VERTS; + WorkingCube->VertexList = CubeVerts; + WorkingCube->NumFaces = NUM_CUBE_FACES; + WorkingCube->Rotate = InitialRotate[i]; + WorkingCube->Move.MoveX = INT_TO_FIXED(InitialMove[i].MoveX); + WorkingCube->Move.MoveY = INT_TO_FIXED(InitialMove[i].MoveY); + WorkingCube->Move.MoveZ = INT_TO_FIXED(InitialMove[i].MoveZ); + WorkingCube->Move.MinX = INT_TO_FIXED(InitialMove[i].MinX); + WorkingCube->Move.MinY = INT_TO_FIXED(InitialMove[i].MinY); + WorkingCube->Move.MinZ = INT_TO_FIXED(InitialMove[i].MinZ); + WorkingCube->Move.MaxX = INT_TO_FIXED(InitialMove[i].MaxX); + WorkingCube->Move.MaxY = INT_TO_FIXED(InitialMove[i].MaxY); + 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); } + if ((WorkingCube->ProjectedVertexList = + malloc(NUM_CUBE_VERTS*sizeof(Point3))) == NULL) { + 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); } + if ((WorkingCube->FaceList = + malloc(NUM_CUBE_FACES*sizeof(Face))) == NULL) { + printf("Couldn't get memory\n"); exit(1); } + /* Initialize the faces */ + for (j=0; jFaceList[j].VertNums = VertNumList[j]; + WorkingCube->FaceList[j].NumVerts = VertsInFace[j]; + WorkingCube->FaceList[j].Color = Colors[i][j]; + } + ObjectList[NumObjects++] = (Object *)WorkingCube; + } +} +``` diff --git a/52-06.md b/52-06.md index edf9ec1..4f2c6b3 100644 --- a/52-06.md +++ b/52-06.md @@ -12,177 +12,181 @@ pages: 981-984 **LISTING 52.9 L52-9.ASM** - ; 386-specific fixed point multiply and divide. - ; - ; C near-callable as: Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2); - ; Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor); - ; - ; Tested with TASM - ; - .model small - .386 - .code - public _FixedMul,_FixedDiv - ; Multiplies two fixed-point values together. - FMparms struc - dw 2 dup(?) ;return address & pushed BP - M1 dd ? - M2 dd ? - FMparms ends - align 2 - _FixedMul proc near - push bp - mov bp,sp - mov eax,[bp+M1] - imul dword ptr [bp+M2] ;multiply - add eax,8000h ;round by adding 2^(-16) - adc edx,0 ;whole part of result is in DX - shr eax,16 ;put the fractional part in AX - pop bp - ret - _FixedMul endp - ; Divides one fixed-point value by another. - FDparms struc - dw 2 dup(?) ;return address & pushed BP - Dividend dd ? - Divisor dd ? - FDparms ends - align 2 - _FixedDiv proc near - push bp - mov bp,sp - sub cx,cx ;assume positive result - mov eax,[bp+Dividend] - and eax,eax ;positive dividend? - jns FDP1 ;yes - 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 in EAX - rol eax,16 ;put fractional part of dividend in - ; high word of EAX - mov dx,ax ;put whole part of dividend in DX - sub ax,ax ;clear low word of EAX - mov ebx,dword ptr [bp+Divisor] - and ebx,ebx ;positive divisor? - jns FDP2 ;yes - 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 - adc ebx,0 ; even - dec ebx - cmp ebx,edx ;set Carry if remainder is at least - adc eax,0 ; half as large as the divisor, then - ; use that to round up if necessary - and cx,cx ;should the result be made negative? - jz FDP3 ;no - neg eax ;yes, negate it - FDP3: mov edx,eax ;return result in DX:AX; fractional - ; part is already in AX - shr edx,16 ;whole part of result in DX - pop bp - ret - _FixedDiv endp - end +```nasm +; 386-specific fixed point multiply and divide. +; +; C near-callable as: Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2); +; Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor); +; +; Tested with TASM +; + .model small + .386 + .code + public _FixedMul,_FixedDiv +; Multiplies two fixed-point values together. +FMparms struc + dw 2 dup(?) ;return address & pushed BP +M1 dd ? +M2 dd ? +FMparms ends + align 2 +_FixedMul proc near + push bp + mov bp,sp + mov eax,[bp+M1] + imul dword ptr [bp+M2] ;multiply + add eax,8000h ;round by adding 2^(-16) + adc edx,0 ;whole part of result is in DX + shr eax,16 ;put the fractional part in AX + pop bp + ret +_FixedMul endp +; Divides one fixed-point value by another. +FDparms struc + dw 2 dup(?) ;return address & pushed BP +Dividend dd ? +Divisor dd ? +FDparms ends + align 2 +_FixedDiv proc near + push bp + mov bp,sp + sub cx,cx ;assume positive result + mov eax,[bp+Dividend] + and eax,eax ;positive dividend? + jns FDP1 ;yes + 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 in EAX + rol eax,16 ;put fractional part of dividend in + ; high word of EAX + mov dx,ax ;put whole part of dividend in DX + sub ax,ax ;clear low word of EAX + mov ebx,dword ptr [bp+Divisor] + and ebx,ebx ;positive divisor? + jns FDP2 ;yes + 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 + adc ebx,0 ; even + dec ebx + cmp ebx,edx ;set Carry if remainder is at least + adc eax,0 ; half as large as the divisor, then + ; use that to round up if necessary + and cx,cx ;should the result be made negative? + jz FDP3 ;no + neg eax ;yes, negate it +FDP3: mov edx,eax ;return result in DX:AX; fractional + ; part is already in AX + shr edx,16 ;whole part of result in DX + pop bp + ret +_FixedDiv endp + end +``` **LISTING 52.10 POLYGON.H** - /* POLYGON.H: Header file for polygon-filling code, also includes - a number of useful items for 3-D animation. */ +```c +/* POLYGON.H: Header file for polygon-filling code, also includes + a number of useful items for 3-D animation. */ - #define MAX_OBJECTS 100 /* max simultaneous # objects supported */ - #define MAX_POLY_LENGTH 4 /* four vertices is the max per poly */ - #define SCREEN_WIDTH 320 - #define SCREEN_HEIGHT 240 - #define PAGE0_START_OFFSET 0 - #define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4) - #define NUM_CUBE_VERTS 8 /* # of vertices per cube */ - #define NUM_CUBE_FACES 6 /* # of faces per cube */ - /* Ratio: distance from viewpoint to projection plane / width of - projection plane. Defines the width of the field of view. Lower - absolute values = wider fields of view; higher values = narrower */ - #define PROJECTION_RATIO -2.0 /* negative because visible Z - coordinates are negative */ - /* Draws the polygon described by the point list PointList in color - Color with all vertices offset by (X,Y) */ - #define DRAW_POLYGON(PointList,NumPoints,Color,X,Y) \ - Polygon.Length = NumPoints; Polygon.PointPtr = PointList; \ - FillConvexPolygon(&Polygon, Color, X, Y); - #define INT_TO_FIXED(x) (((long)(int)x) << 16) - #define DOUBLE_TO_FIXED(x) ((long) (x * 65536.0 + 0.5)) +#define MAX_OBJECTS 100 /* max simultaneous # objects supported */ +#define MAX_POLY_LENGTH 4 /* four vertices is the max per poly */ +#define SCREEN_WIDTH 320 +#define SCREEN_HEIGHT 240 +#define PAGE0_START_OFFSET 0 +#define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4) +#define NUM_CUBE_VERTS 8 /* # of vertices per cube */ +#define NUM_CUBE_FACES 6 /* # of faces per cube */ +/* Ratio: distance from viewpoint to projection plane / width of + projection plane. Defines the width of the field of view. Lower + absolute values = wider fields of view; higher values = narrower */ +#define PROJECTION_RATIO -2.0 /* negative because visible Z + coordinates are negative */ +/* Draws the polygon described by the point list PointList in color + Color with all vertices offset by (X,Y) */ +#define DRAW_POLYGON(PointList,NumPoints,Color,X,Y) \ + Polygon.Length = NumPoints; Polygon.PointPtr = PointList; \ + FillConvexPolygon(&Polygon, Color, X, Y); +#define INT_TO_FIXED(x) (((long)(int)x) << 16) +#define DOUBLE_TO_FIXED(x) ((long) (x * 65536.0 + 0.5)) - typedef long Fixedpoint; - typedef Fixedpoint Xform[3][4]; - /* Describes a single 2D point */ - typedef struct { int X; int Y; } Point; - /* Describes a single 3D point in homogeneous coordinates; the W - coordinate isn't present, though; assumed to be 1 and implied */ - typedef struct { Fixedpoint X, Y, Z; } Point3; - typedef struct { int X; int Y; int Z; } IntPoint3; - /* Describes a series of points (used to store a list of vertices that - describe a polygon; each vertex is assumed to connect to the two - adjacent vertices; last vertex is assumed to connect to first) */ - typedef struct { int Length; Point * PointPtr; } PointListHeader; - /* Describes the beginning and ending X coordinates of a single - horizontal line */ - typedef struct { int XStart; int XEnd; } HLine; - /* Describes a Length-long series of horizontal lines, all assumed to - be on contiguous scan lines starting at YStart and proceeding - downward (used to describe a scan-converted polygon to the - low-level hardware-dependent drawing code). */ - typedef struct { int Length; int YStart; HLine * HLinePtr;} HLineList; - typedef struct { int Left, Top, Right, Bottom; } Rect; - /* structure describing one face of an object (one polygon) */ - typedef struct { int * VertNums; int NumVerts; int Color; } Face; - typedef struct { double RotateX, RotateY, RotateZ; } RotateControl; - typedef struct { Fixedpoint MoveX, MoveY, MoveZ, MinX, MinY, MinZ, - MaxX, MaxY, MaxZ; } MoveControl; - /* fields common to every object */ - #define BASE_OBJECT \ - void (*DrawFunc)(); /* draws object */ \ - void (*RecalcFunc)(); /* prepares object for drawing */ \ - void (*MoveFunc)(); /* moves object */ \ - int RecalcXform; /* 1 to indicate need to recalc */ \ - Rect EraseRect[2]; /* rectangle to erase in each page */ - /* basic object */ - typedef struct { BASE_OBJECT } Object; - /* structure describing a polygon-based object */ - typedef struct { - BASE_OBJECT - int RDelayCount, RDelayCountBase; /* controls rotation speed */ - int MDelayCount, MDelayCountBase; /* controls movement speed */ - Xform XformToWorld; /* transform from object->world space */ - Xform XformToView; /* transform from object->view space */ - RotateControl Rotate; /* controls rotation change over time */ - MoveControl Move; /* controls object movement over time */ - int NumVerts; /* # vertices in VertexList */ - Point3 * VertexList; /* untransformed vertices */ - Point3 * XformedVertexList; /* transformed into view space */ - Point3 * ProjectedVertexList; /* projected into screen space */ - Point * ScreenVertexList; /* converted to screen coordinates */ - int NumFaces; /* # of faces in object */ - Face * FaceList; /* pointer to face info */ - } PObject; +typedef long Fixedpoint; +typedef Fixedpoint Xform[3][4]; +/* Describes a single 2D point */ +typedef struct { int X; int Y; } Point; +/* Describes a single 3D point in homogeneous coordinates; the W + coordinate isn't present, though; assumed to be 1 and implied */ +typedef struct { Fixedpoint X, Y, Z; } Point3; +typedef struct { int X; int Y; int Z; } IntPoint3; +/* Describes a series of points (used to store a list of vertices that + describe a polygon; each vertex is assumed to connect to the two + adjacent vertices; last vertex is assumed to connect to first) */ +typedef struct { int Length; Point * PointPtr; } PointListHeader; +/* Describes the beginning and ending X coordinates of a single + horizontal line */ +typedef struct { int XStart; int XEnd; } HLine; +/* Describes a Length-long series of horizontal lines, all assumed to + be on contiguous scan lines starting at YStart and proceeding + downward (used to describe a scan-converted polygon to the + low-level hardware-dependent drawing code). */ +typedef struct { int Length; int YStart; HLine * HLinePtr;} HLineList; +typedef struct { int Left, Top, Right, Bottom; } Rect; +/* structure describing one face of an object (one polygon) */ +typedef struct { int * VertNums; int NumVerts; int Color; } Face; +typedef struct { double RotateX, RotateY, RotateZ; } RotateControl; +typedef struct { Fixedpoint MoveX, MoveY, MoveZ, MinX, MinY, MinZ, + MaxX, MaxY, MaxZ; } MoveControl; +/* fields common to every object */ +#define BASE_OBJECT \ + void (*DrawFunc)(); /* draws object */ \ + void (*RecalcFunc)(); /* prepares object for drawing */ \ + void (*MoveFunc)(); /* moves object */ \ + int RecalcXform; /* 1 to indicate need to recalc */ \ + Rect EraseRect[2]; /* rectangle to erase in each page */ +/* basic object */ +typedef struct { BASE_OBJECT } Object; +/* structure describing a polygon-based object */ +typedef struct { + BASE_OBJECT + int RDelayCount, RDelayCountBase; /* controls rotation speed */ + int MDelayCount, MDelayCountBase; /* controls movement speed */ + Xform XformToWorld; /* transform from object->world space */ + Xform XformToView; /* transform from object->view space */ + RotateControl Rotate; /* controls rotation change over time */ + MoveControl Move; /* controls object movement over time */ + int NumVerts; /* # vertices in VertexList */ + Point3 * VertexList; /* untransformed vertices */ + Point3 * XformedVertexList; /* transformed into view space */ + Point3 * ProjectedVertexList; /* projected into screen space */ + Point * ScreenVertexList; /* converted to screen coordinates */ + int NumFaces; /* # of faces in object */ + Face * FaceList; /* pointer to face info */ +} PObject; - extern void XformVec(Xform, Fixedpoint *, Fixedpoint *); - extern void ConcatXforms(Xform, Xform, Xform); - extern int FillConvexPolygon(PointListHeader *, int, int, int); - extern void Set320x240Mode(void); - extern void ShowPage(unsigned int); - extern void FillRectangleX(int, int, int, int, unsigned int, int); - extern void XformAndProjectPObject(PObject *); - extern void DrawPObject(PObject *); - extern void AppendRotationX(Xform, double); - extern void AppendRotationY(Xform, double); - extern void AppendRotationZ(Xform, double); - extern near Fixedpoint FixedMul(Fixedpoint, Fixedpoint); - extern near Fixedpoint FixedDiv(Fixedpoint, Fixedpoint); - extern void InitializeFixedPoint(void); - extern void RotateAndMovePObject(PObject *); - extern void InitializeCubes(void); - extern int DisplayedPage, NonDisplayedPage, RecalcAllXforms; - extern int NumObjects; - extern Xform WorldViewXform; - extern Object *ObjectList[]; - extern Point3 CubeVerts[]; +extern void XformVec(Xform, Fixedpoint *, Fixedpoint *); +extern void ConcatXforms(Xform, Xform, Xform); +extern int FillConvexPolygon(PointListHeader *, int, int, int); +extern void Set320x240Mode(void); +extern void ShowPage(unsigned int); +extern void FillRectangleX(int, int, int, int, unsigned int, int); +extern void XformAndProjectPObject(PObject *); +extern void DrawPObject(PObject *); +extern void AppendRotationX(Xform, double); +extern void AppendRotationY(Xform, double); +extern void AppendRotationZ(Xform, double); +extern near Fixedpoint FixedMul(Fixedpoint, Fixedpoint); +extern near Fixedpoint FixedDiv(Fixedpoint, Fixedpoint); +extern void InitializeFixedPoint(void); +extern void RotateAndMovePObject(PObject *); +extern void InitializeCubes(void); +extern int DisplayedPage, NonDisplayedPage, RecalcAllXforms; +extern int NumObjects; +extern Xform WorldViewXform; +extern Object *ObjectList[]; +extern Point3 CubeVerts[]; +``` diff --git a/53-02.md b/53-02.md index fdbb839..4ade139 100644 --- a/53-02.md +++ b/53-02.md @@ -12,390 +12,392 @@ pages: 992-999 **LISTING 53.1 FIXED.ASM** - ; 386-specific fixed point routines. - ; Tested with TASM - ROUNDING-ON equ 1 ;1 for rounding, 0 for no rounding - ;no rounding is faster, rounding is - ; more accurate - ALIGNMENT equ 2 - .model small - .386 - .code - ;===================================================================== - ; Multiplies two fixed-point values together. - ; C near-callable as: - ; Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2); - ; Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor); - FMparms struc - dw 2 dup(?) ;return address & pushed BP - M1 dd ? - M2 dd ? - FMparms ends - align ALIGNMENT - public -FixedMul - -FixedMul proc near - push bp - mov bp,sp - mov eax,[bp+M1] - imul dword ptr [bp+M2] ;multiply - if ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;ROUNDING-ON - shr eax,16 ;put the fractional part in AX - pop bp - ret - -FixedMul endp - ;===================================================================== - ; Divides one fixed-point value by another. - ; C near-callable as: - ; Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor); - FDparms struc - dw 2 dup(?) ;return address & pushed BP - Dividend dd ? - Divisor dd ? - FDparms ends - align ALIGNMENT - public -FixedDiv - -FixedDiv proc near - push bp - mov bp,sp +```nasm +; 386-specific fixed point routines. +; Tested with TASM +ROUNDING-ON equ 1 ;1 for rounding, 0 for no rounding + ;no rounding is faster, rounding is + ; more accurate +ALIGNMENT equ 2 + .model small + .386 + .code +;===================================================================== +; Multiplies two fixed-point values together. +; C near-callable as: +; Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2); +; Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor); +FMparms struc + dw 2 dup(?) ;return address & pushed BP +M1 dd ? +M2 dd ? +FMparms ends + align ALIGNMENT + public -FixedMul +-FixedMul proc near + push bp + mov bp,sp + mov eax,[bp+M1] + imul dword ptr [bp+M2] ;multiply +if ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;ROUNDING-ON + shr eax,16 ;put the fractional part in AX + pop bp + ret +-FixedMul endp +;===================================================================== +; Divides one fixed-point value by another. +; C near-callable as: +; Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor); +FDparms struc + dw 2 dup(?) ;return address & pushed BP +Dividend dd ? +Divisor dd ? +FDparms ends + align ALIGNMENT +public -FixedDiv +-FixedDiv proc near + push bp + mov bp,sp - if ROUNDING-ON - sub cx,cx ;assume positive result - mov eax,[bp+Dividend] - and eax,eax ;positive dividend? - jns FDP1 ;yes - 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 - ; in EAX - rol eax,16 ;put fractional part of dividend in - ; high word of EAX - mov dx,ax ;put whole part of dividend in DX - sub ax,ax ;clear low word of EAX - mov ebx,dword ptr [bp+Divisor] - and ebx,ebx ;positive divisor? - jns FDP2 ;yes - 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 - adc ebx,0 ; even - dec ebx - cmp ebx,edx ;set Carry if remainder is at least - adc eax,0 ; half as large as the divisor, then - ; use that to round up if necessary - and cx,cx ;should the result be made negative? - jz FDP3 ;no - neg eax ;yes, negate it - FDP3: - else ; !ROUNDING-ON - mov edx,[bp+Dividend] - sub eax,eax - shrd eax,edx,16 ;position so that result ends up - sar edx,16 ; in EAX - idivdword ptr [bp+Divisor] - endif ;ROUNDING-ON - shld edx,eax,16 ;whole part of result in DX; - ; fractional part is already in AX - pop bp - ret - -FixedDiv endp - ;===================================================================== - ; Returns the sine and cosine of an angle. - ; C near-callable as: - ;void CosSin(TAngle Angle, Fixedpoint *Cos, Fixedpoint *); +if ROUNDING-ON + sub cx,cx ;assume positive result + mov eax,[bp+Dividend] + and eax,eax ;positive dividend? + jns FDP1 ;yes + 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 + ; in EAX + rol eax,16 ;put fractional part of dividend in + ; high word of EAX + mov dx,ax ;put whole part of dividend in DX + sub ax,ax ;clear low word of EAX + mov ebx,dword ptr [bp+Divisor] + and ebx,ebx ;positive divisor? + jns FDP2 ;yes + 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 + adc ebx,0 ; even + dec ebx + cmp ebx,edx ;set Carry if remainder is at least + adc eax,0 ; half as large as the divisor, then + ; use that to round up if necessary + and cx,cx ;should the result be made negative? + jz FDP3 ;no + neg eax ;yes, negate it +FDP3: +else ; !ROUNDING-ON + mov edx,[bp+Dividend] + sub eax,eax + shrd eax,edx,16 ;position so that result ends up + sar edx,16 ; in EAX + idivdword ptr [bp+Divisor] +endif ;ROUNDING-ON + shld edx,eax,16 ;whole part of result in DX; + ; fractional part is already in AX + pop bp + ret +-FixedDiv endp +;===================================================================== +; Returns the sine and cosine of an angle. +; C near-callable as: +;void CosSin(TAngle Angle, Fixedpoint *Cos, Fixedpoint *); - align ALIGNMENT - CosTable label dword - include costable.inc +align ALIGNMENT +CosTable label dword +include costable.inc - SCparms struc - dw 2 dup(?) ;return address & pushed BP - Angle dw ? ;angle to calculate sine & cosine for - Cos dw ? ;pointer to cos destination - Sin dw ? ;pointer to sin destination - SCparms ends +SCparms struc + dw 2 dup(?) ;return address & pushed BP +Angle dw ? ;angle to calculate sine & cosine for +Cos dw ? ;pointer to cos destination +Sin dw ? ;pointer to sin destination +SCparms ends - align ALIGNMENT - public -CosSin - -CosSinprocnear - push bp ;preserve stack frame - mov bp,sp ;set up local stack frame + align ALIGNMENT + public -CosSin +-CosSinprocnear + push bp ;preserve stack frame + mov bp,sp ;set up local stack frame - mov bx,[bp].Angle - 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 - js MakePos - jmp short CheckInRange + mov bx,[bp].Angle + 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 + js MakePos + jmp short CheckInRange - align ALIGNMENT - MakeInRange: ;make sure angle is no more than 2*pi - sub bx,360*10 - CheckInRange: - cmp bx,360*10 - jg MakeInRange +align ALIGNMENT +MakeInRange: ;make sure angle is no more than 2*pi + sub bx,360*10 +CheckInRange: +cmp bx,360*10 +jg MakeInRange - cmp bx,180*10 ;figure out which quadrant - ja BottomHalf ;quadrant 2 or 3 - cmp bx,90*10 ;quadrant 0 or 1 - ja Quadrant1 - ;quadrant 0 - shl bx,2 - move ax,CosTable[bx] ;look up sine - neg bx ;sin(Angle) = cos(90-Angle) - move dx,CosTable[bx+90*10*4] ;look up cosine - jmp short CSDone + cmp bx,180*10 ;figure out which quadrant + ja BottomHalf ;quadrant 2 or 3 + cmp bx,90*10 ;quadrant 0 or 1 + ja Quadrant1 + ;quadrant 0 + shl bx,2 + move ax,CosTable[bx] ;look up sine + neg bx ;sin(Angle) = cos(90-Angle) + move dx,CosTable[bx+90*10*4] ;look up cosine + jmp short CSDone - align ALIGNMENT - Quadrant1: - neg bx - add bx,180*10 ;convert to angle between 0 and 90 - shl bx,2 - mov eax,CosTable[bx] ;look up cosine - neg eax ;negative in this quadrant - neg bx ;sin(Angle) = cos(90-Angle) - move dx,CosTable[bx+90*10*4] ;look up cosine - jmp short CSDone + align ALIGNMENT +Quadrant1: + neg bx + add bx,180*10 ;convert to angle between 0 and 90 + shl bx,2 + mov eax,CosTable[bx] ;look up cosine + neg eax ;negative in this quadrant + neg bx ;sin(Angle) = cos(90-Angle) + move dx,CosTable[bx+90*10*4] ;look up cosine + jmp short CSDone - align ALIGNMENT - BottomHalf: ;quadrant 2 or 3 - neg bx - add bx,360*10 ;convert to angle between 0 and 180 - cmp bx,90*10 ;quadrant 2 or 3 - ja Quadrant2 - ;quadrant 3 - shl bx, 2 - mov eax,CosTable[bx] ;look up cosine - neg bx ;sin(Angle) = cos(90-Angle) - movedx,CosTable[90*10*4+bx] ;look up sine - nege dx ;negative in this quadrant - jmp short CSDone + align ALIGNMENT +BottomHalf: ;quadrant 2 or 3 + neg bx + add bx,360*10 ;convert to angle between 0 and 180 + cmp bx,90*10 ;quadrant 2 or 3 + ja Quadrant2 + ;quadrant 3 + shl bx, 2 + mov eax,CosTable[bx] ;look up cosine + neg bx ;sin(Angle) = cos(90-Angle) + movedx,CosTable[90*10*4+bx] ;look up sine + nege dx ;negative in this quadrant + jmp short CSDone - align ALIGNMENT - Quadrant2: - neg bx - add bx,180*10 ;convert to angle between 0 and 90 - shl bx,2 - mov eax,CosTable[bx] ;look up cosine - neg eax ;negative in this quadrant - neg bx ;sin(Angle) = cos(90-Angle) - move dx,CosTable[90*10*4+bx] ;look up sine - nege dx ;negative in this quadrant - CSDone: - mov bx,[bp].Cos - mov [bx],eax - mov bx,[bp].Sin - mov [bx],edx + align ALIGNMENT +Quadrant2: + neg bx + add bx,180*10 ;convert to angle between 0 and 90 + shl bx,2 + mov eax,CosTable[bx] ;look up cosine + neg eax ;negative in this quadrant + neg bx ;sin(Angle) = cos(90-Angle) + move dx,CosTable[90*10*4+bx] ;look up sine + nege dx ;negative in this quadrant +CSDone: + mov bx,[bp].Cos + mov [bx],eax + mov bx,[bp].Sin + mov [bx],edx - pop bp;restore stack frame - ret - -CosSinendp - ;===================================================================== - ; 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 - ; the W coordinate of the destination. - ; C near-callable as: - ; void XformVec(Xform WorkingXform, Fixedpoint *SourceVec, - ; Fixedpoint *DestVec); - ; - ; This assembly code is equivalent to this C code: - ; int i; - ; - ; for (i=0; i<3; i++) - ; DestVec[i] = FixedMul(WorkingXform[i][0], SourceVec[0]) + - ; FixedMul(WorkingXform[i][1], SourceVec[1]) + - ; FixedMul(WorkingXform[i][2], SourceVec[2]) + - ; WorkingXform[i][3]; /* no need to multiply by W = 1 */ + pop bp;restore stack frame +ret +-CosSinendp +;===================================================================== +; 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 +; the W coordinate of the destination. +; C near-callable as: +; void XformVec(Xform WorkingXform, Fixedpoint *SourceVec, +; Fixedpoint *DestVec); +; +; This assembly code is equivalent to this C code: +; int i; +; +; for (i=0; i<3; i++) +; DestVec[i] = FixedMul(WorkingXform[i][0], SourceVec[0]) + +; FixedMul(WorkingXform[i][1], SourceVec[1]) + +; FixedMul(WorkingXform[i][2], SourceVec[2]) + +; WorkingXform[i][3]; /* no need to multiply by W = 1 */ - XVparms struc - dw 2 dup(?) ;return address & pushed BP - WorkingXform dw ? ;pointer to transform matrix - SourceVec dw ? ;pointer to source vector - DestVec dw ? ;pointer to destination vector - XVparms ends +XVparms struc + dw 2 dup(?) ;return address & pushed BP +WorkingXform dw ? ;pointer to transform matrix +SourceVec dw ? ;pointer to source vector +DestVec dw ? ;pointer to destination vector +XVparms ends - align ALIGNMENT - public -XformVec - -XformVec proc near - push bp ;preserve stack frame - mov bp,sp ;set up local stack frame - push si ;preserve register variables - push di + align ALIGNMENT + public -XformVec +-XformVec proc near + push bp ;preserve stack frame + mov bp,sp ;set up local stack frame + push si ;preserve register variables + push di - mov si,[bp].WorkingXform ;SI points to xform matrix - mov bx,[bp].SourceVec ;BX points to source vector - mov di,[bp].DestVec ;DI points to dest vector + mov si,[bp].WorkingXform ;SI points to xform matrix + mov bx,[bp].SourceVec ;BX points to source vector + mov di,[bp].DestVec ;DI points to dest vector - soff=0 - doff=0 - REPT 3 ;do once each for dest X, Y, and Z - mov eax,[si+soff] ;column 0 entry on this row - imul dword ptr [bx] ;xform entry times source X entry - if ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;ROUNDING-ON - shrd eax,edx,16;shift the result back to 16.16 form - mov ecx,eax ;set running total +soff=0 +doff=0 + REPT 3 ;do once each for dest X, Y, and Z + mov eax,[si+soff] ;column 0 entry on this row + imul dword ptr [bx] ;xform entry times source X entry +if ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;ROUNDING-ON + shrd eax,edx,16;shift the result back to 16.16 form + mov ecx,eax ;set running total - mov eax,[si+soff+4] ;column 1 entry on this row - imul dword ptr [bx+4] ;xform entry times source Y entry - if ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - add ecx,eax ;running total for this row + mov eax,[si+soff+4] ;column 1 entry on this row + imul dword ptr [bx+4] ;xform entry times source Y entry +if ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + add ecx,eax ;running total for this row - mov eax,[si+soff+8] ;column 2 entry on this row - imul dword ptr [bx+8] ;xform entry times source Z entry - if ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - add ecx,eax ;running total for this row + mov eax,[si+soff+8] ;column 2 entry on this row + imul dword ptr [bx+8] ;xform entry times source Z entry +if ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + add ecx,eax ;running total for this row - add ecx,[si+soff+12] ;add in translation - mov[di+doff],ecx ;save the result in the dest vector - soff=soff+16 - doff=doff+4 - ENDM + add ecx,[si+soff+12] ;add in translation + mov[di+doff],ecx ;save the result in the dest vector +soff=soff+16 +doff=doff+4 +ENDM - popdi;restore register variables - popsi - popbp;restore stack frame - ret - -XformVecendp - ;===================================================================== - ; 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 - ; of the destination. - ; C near-callable as: - ; void ConcatXforms(Xform SourceXform1, Xform SourceXform2, - ; Xform DestXform) - ; - ; This assembly code is equivalent to this C code: - ; int i, j; - ; - ; for (i=0; i<3; i++) { - ; for (j=0; j<3; j++) - ; DestXform[i][j] = - ; FixedMul(SourceXform1[i][0], SourceXform2[0][j]) + - ; FixedMul(SourceXform1[i][1], SourceXform2[1][j]) + - ; FixedMul(SourceXform1[i][2], SourceXform2[2][j]); - ; DestXform[i][3] = - ; FixedMul(SourceXform1[i][0], SourceXform2[0][3]) + - ; FixedMul(SourceXform1[i][1], SourceXform2[1][3]) + - ; FixedMul(SourceXform1[i][2], SourceXform2[2][3]) + - ; SourceXform1[i][3]; - ; } +popdi;restore register variables +popsi +popbp;restore stack frame +ret +-XformVecendp +;===================================================================== +; 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 +; of the destination. +; C near-callable as: +; void ConcatXforms(Xform SourceXform1, Xform SourceXform2, +; Xform DestXform) +; +; This assembly code is equivalent to this C code: +; int i, j; +; +; for (i=0; i<3; i++) { +; for (j=0; j<3; j++) +; DestXform[i][j] = +; FixedMul(SourceXform1[i][0], SourceXform2[0][j]) + +; FixedMul(SourceXform1[i][1], SourceXform2[1][j]) + +; FixedMul(SourceXform1[i][2], SourceXform2[2][j]); +; DestXform[i][3] = +; FixedMul(SourceXform1[i][0], SourceXform2[0][3]) + +; FixedMul(SourceXform1[i][1], SourceXform2[1][3]) + +; FixedMul(SourceXform1[i][2], SourceXform2[2][3]) + +; SourceXform1[i][3]; +; } - CXparms struc - dw 2 dup(?) ;return address & pushed BP - SourceXform1 dw ? ;pointer to first source xform matrix - SourceXform2 dw ? ;pointer to second source xform matrix - DestXform dw ? ;pointer to destination xform matrix - CXparms ends +CXparms struc + dw 2 dup(?) ;return address & pushed BP +SourceXform1 dw ? ;pointer to first source xform matrix +SourceXform2 dw ? ;pointer to second source xform matrix +DestXform dw ? ;pointer to destination xform matrix +CXparms ends - align ALIGNMENT - public -ConcatXforms - -ConcatXforms proc near - push bp ;preserve stack frame - mov bp,sp ;set up local stack frame - push si ;preserve register variables - pushdi + align ALIGNMENT + public -ConcatXforms +-ConcatXforms proc near + push bp ;preserve stack frame + mov bp,sp ;set up local stack frame + push si ;preserve register variables +pushdi - mov bx,[bp].SourceXform2 ;BX points to xform2 matrix - mov si,[bp].SourceXform1 ;SI points to xform1 matrix - mov di,[bp].DestXform ;DI points to dest xform matrix + mov bx,[bp].SourceXform2 ;BX points to xform2 matrix + mov si,[bp].SourceXform1 ;SI points to xform1 matrix + mov di,[bp].DestXform ;DI points to dest xform matrix - roff=0 ;row offset - REPT 3 ;once for each row - coff=0 ;column offset - REPT 3 ;once for each of the first 3 columns, - ; assuming 0 as the bottom entry (no - ; translation) - mov eax,[si+roff] ;column 0 entry on this row - imul dword ptr [bx+coff];times row 0 entry in column - if ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adcedx,0;whole part of result is in DX - endif ;ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - mov ecx,eax ;set running total +roff=0 ;row offset + REPT 3 ;once for each row +coff=0 ;column offset + REPT 3 ;once for each of the first 3 columns, + ; assuming 0 as the bottom entry (no +; translation) + mov eax,[si+roff] ;column 0 entry on this row + imul dword ptr [bx+coff];times row 0 entry in column +if ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adcedx,0;whole part of result is in DX +endif ;ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + mov ecx,eax ;set running total - mov eax,[si+roff+4] ;column 1 entry on this row - imul dword ptr [bx+coff+16];times row 1 entry in col - if ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - add ecx,eax ;running total + mov eax,[si+roff+4] ;column 1 entry on this row + imul dword ptr [bx+coff+16];times row 1 entry in col +if ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + add ecx,eax ;running total - mov eax,[si+roff+8] ;column 2 entry on this row - imul dword ptr [bx+coff+32];times row 2 entry in col - if ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - add ecx,eax ;running total + mov eax,[si+roff+8] ;column 2 entry on this row + imul dword ptr [bx+coff+32];times row 2 entry in col +if ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + add ecx,eax ;running total - mov[di+coff+roff],ecx ;save the result in dest matrix - coff=coff+4 ;point to next col in xform2 & dest - ENDM - ;now do the fourth column, assuming - ; 1 as the bottom entry, causing - ; translation to be performed - mov eax,[si+roff] ;column 0 entry on this row - imul dword ptr [bx+coff] ;times row 0 entry in column - if ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - mov ecx,eax ;set running total + mov[di+coff+roff],ecx ;save the result in dest matrix +coff=coff+4 ;point to next col in xform2 & dest +ENDM + ;now do the fourth column, assuming + ; 1 as the bottom entry, causing + ; translation to be performed + mov eax,[si+roff] ;column 0 entry on this row + imul dword ptr [bx+coff] ;times row 0 entry in column +if ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + mov ecx,eax ;set running total - mov eax,[si+roff+4] ;column 1 entry on this row - imul dword ptr [bx+coff+16];times row 1 entry in col - if ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - add ecx,eax ;running total + mov eax,[si+roff+4] ;column 1 entry on this row + imul dword ptr [bx+coff+16];times row 1 entry in col +if ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + add ecx,eax ;running total - mov eax,[si+roff+8] ;column 2 entry on this row - imul dword ptr [bx+coff+32];times row 2 entry in col - if ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - addecx,eax;running total + mov eax,[si+roff+8] ;column 2 entry on this row + imul dword ptr [bx+coff+32];times row 2 entry in col +if ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form +addecx,eax;running total - addecx,[si+roff+12];add in translation +addecx,[si+roff+12];add in translation - mov[di+coff+roff],ecx;save the result in dest matrix - coff=coff+4 ;point to next col in xform2 & dest +mov[di+coff+roff],ecx;save the result in dest matrix +coff=coff+4 ;point to next col in xform2 & dest - roff=roff+16 ;point to next col in xform2 & dest - ENDM +roff=roff+16 ;point to next col in xform2 & dest +ENDM - popdi;restore register variables - popsi - popbp;restore stack frame - ret - -ConcatXformsendp - end +popdi;restore register variables +popsi +popbp;restore stack frame +ret +-ConcatXformsendp +end +``` diff --git a/53-04.md b/53-04.md index 7d14848..4a6d61c 100644 --- a/53-04.md +++ b/53-04.md @@ -12,87 +12,89 @@ pages: 1001-1003 **LISTING 53.2 OLIST.C** - /* Object list-related functions. */ - #include - #include "polygon.h" +```c +/* Object list-related functions. */ +#include +#include "polygon.h" - /* Set up the empty object list, with sentinels at both ends to - terminate searches */ - void InitializeObjectList() - { - ObjectListStart.NextObject = &ObjectListEnd; - ObjectListStart.PreviousObject = NULL; - ObjectListStart.CenterInView.Z = INT-TO-FIXED(-32768); - ObjectListEnd.NextObject = NULL; - ObjectListEnd.PreviousObject = &ObjectListStart; - ObjectListEnd.CenterInView.Z = 0x7FFFFFFFL; - NumObjects = 0; - } +/* Set up the empty object list, with sentinels at both ends to + terminate searches */ +void InitializeObjectList() +{ + ObjectListStart.NextObject = &ObjectListEnd; + ObjectListStart.PreviousObject = NULL; + ObjectListStart.CenterInView.Z = INT-TO-FIXED(-32768); + ObjectListEnd.NextObject = NULL; + ObjectListEnd.PreviousObject = &ObjectListStart; + ObjectListEnd.CenterInView.Z = 0x7FFFFFFFL; + NumObjects = 0; +} - /* Adds an object to the object list, sorted by center Z coord. */ - void AddObject(Object *ObjectPtr) - { - Object *ObjectListPtr = ObjectListStart.NextObject; +/* Adds an object to the object list, sorted by center Z coord. */ +void AddObject(Object *ObjectPtr) +{ + Object *ObjectListPtr = ObjectListStart.NextObject; - /* Find the insertion point. Guaranteed to terminate because of - the end sentinel */ - while (ObjectPtr->CenterInView.Z > ObjectListPtr->CenterInView.Z) { - ObjectListPtr = ObjectListPtr->NextObject; - } + /* Find the insertion point. Guaranteed to terminate because of + the end sentinel */ + while (ObjectPtr->CenterInView.Z > ObjectListPtr->CenterInView.Z) { + ObjectListPtr = ObjectListPtr->NextObject; + } - /* Link in the new object */ - ObjectListPtr->PreviousObject->NextObject = ObjectPtr; - ObjectPtr->NextObject = ObjectListPtr; - ObjectPtr->PreviousObject = ObjectListPtr->PreviousObject; - ObjectListPtr->PreviousObject = ObjectPtr; - NumObjects++; - } + /* Link in the new object */ + ObjectListPtr->PreviousObject->NextObject = ObjectPtr; + ObjectPtr->NextObject = ObjectListPtr; + ObjectPtr->PreviousObject = ObjectListPtr->PreviousObject; + ObjectListPtr->PreviousObject = ObjectPtr; + NumObjects++; +} - /* Resorts the objects in order of ascending center Z coordinate in view space, - by moving each object in turn to the correct position in the object list. */ - void SortObjects() - { - int i; - Object *ObjectPtr, *ObjectCmpPtr, *NextObjectPtr; +/* Resorts the objects in order of ascending center Z coordinate in view space, + by moving each object in turn to the correct position in the object list. */ +void SortObjects() +{ + int i; + Object *ObjectPtr, *ObjectCmpPtr, *NextObjectPtr; - /* Start checking with the second object */ - ObjectCmpPtr = ObjectListStart.NextObject; - ObjectPtr = ObjectCmpPtr->NextObject; - for (i=1; iCenterInView.Z < ObjectCmpPtr->CenterInView.Z) { - /* Remember where to resume sorting with the next object */ - NextObjectPtr = ObjectPtr->NextObject; - /* Yes, move backward until we find the proper insertion - point. Termination guaranteed because of start sentinel */ - do { - ObjectCmpPtr = ObjectCmpPtr->PreviousObject; - } while (ObjectPtr->CenterInView.Z < - ObjectCmpPtr->CenterInView.Z); + /* Start checking with the second object */ + ObjectCmpPtr = ObjectListStart.NextObject; + ObjectPtr = ObjectCmpPtr->NextObject; + for (i=1; iCenterInView.Z < ObjectCmpPtr->CenterInView.Z) { + /* Remember where to resume sorting with the next object */ + NextObjectPtr = ObjectPtr->NextObject; + /* Yes, move backward until we find the proper insertion + point. Termination guaranteed because of start sentinel */ + do { + ObjectCmpPtr = ObjectCmpPtr->PreviousObject; + } while (ObjectPtr->CenterInView.Z < + ObjectCmpPtr->CenterInView.Z); - /* Now move the object to its new location */ - /* Unlink the object at the old location */ - ObjectPtr->PreviousObject->NextObject = - ObjectPtr->NextObject; - ObjectPtr->NextObject->PreviousObject = - ObjectPtr->PreviousObject; + /* Now move the object to its new location */ + /* Unlink the object at the old location */ + ObjectPtr->PreviousObject->NextObject = + ObjectPtr->NextObject; + ObjectPtr->NextObject->PreviousObject = + ObjectPtr->PreviousObject; - /* Link in the object at the new location */ - ObjectCmpPtr->NextObject->PreviousObject = ObjectPtr; - ObjectPtr->PreviousObject = ObjectCmpPtr; - ObjectPtr->NextObject = ObjectCmpPtr->NextObject; - ObjectCmpPtr->NextObject = ObjectPtr; + /* Link in the object at the new location */ + ObjectCmpPtr->NextObject->PreviousObject = ObjectPtr; + ObjectPtr->PreviousObject = ObjectCmpPtr; + ObjectPtr->NextObject = ObjectCmpPtr->NextObject; + ObjectCmpPtr->NextObject = ObjectPtr; - /* Advance to the next object to sort */ - ObjectCmpPtr = NextObjectPtr->PreviousObject; - ObjectPtr = NextObjectPtr; - } else { - /* Advance to the next object to sort */ - ObjectCmpPtr = ObjectPtr; - ObjectPtr = ObjectPtr->NextObject; - } - } - } + /* Advance to the next object to sort */ + ObjectCmpPtr = NextObjectPtr->PreviousObject; + ObjectPtr = NextObjectPtr; + } else { + /* Advance to the next object to sort */ + ObjectCmpPtr = ObjectPtr; + ObjectPtr = ObjectPtr->NextObject; + } + } +} +``` #### Rounding {#Heading6} diff --git a/54-02.md b/54-02.md index 61def25..adb4a62 100644 --- a/54-02.md +++ b/54-02.md @@ -12,862 +12,864 @@ pages: 1008-1022 **LISTING 54.1 FIXED.ASM** - ; Fixed point routines. - ; Tested with TASM +```nasm +; Fixed point routines. +; Tested with TASM - USE386 equ 1 ;1 for 386-specific opcodes, 0 for - ; 8088 opcodes - MUL-ROUNDING-ON equ 1 ;1 for rounding on multiplies, - ; 0 for no rounding. Not rounding is faster, - ; rounding is more accurate and generally a - ; good idea - DIV-ROUNDING-ON equ 0 ;1 for rounding on divides, - ; 0 for no rounding. Not rounding is faster, - ; rounding is more accurate, but because - ; division is only performed to project to - ; the screen, rounding quotients generally - ; isn't necessary - ALIGNMENT equ 2 +USE386 equ 1 ;1 for 386-specific opcodes, 0 for + ; 8088 opcodes +MUL-ROUNDING-ON equ 1 ;1 for rounding on multiplies, + ; 0 for no rounding. Not rounding is faster, + ; rounding is more accurate and generally a + ; good idea +DIV-ROUNDING-ON equ 0 ;1 for rounding on divides, + ; 0 for no rounding. Not rounding is faster, + ; rounding is more accurate, but because + ; division is only performed to project to + ; the screen, rounding quotients generally + ; isn't necessary +ALIGNMENT equ 2 - .model small - .386 - .code + .model small + .386 + .code - ;===================================================================== - ; Multiplies two fixed-point values together. - ; C near-callable as: - ; Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2); - FMparms struc - dw 2 dup(?) ;return address & pushed BP - M1 dd ? - M2 dd ? - FMparms ends - align ALIGNMENT - public _FixedMul - _FixedMul proc near - push bp - mov bp,sp +;===================================================================== +; Multiplies two fixed-point values together. +; C near-callable as: +; Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2); +FMparms struc + dw 2 dup(?) ;return address & pushed BP +M1 dd ? +M2 dd ? +FMparms ends + align ALIGNMENT + public _FixedMul +_FixedMul proc near + push bp + mov bp,sp - if USE386 +if USE386 - mov eax,[bp+M1] - imul dword ptr [bp+M2] ;multiply - if MUL-ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adcedx,0 ;whole part of result is in DX - endif ;MUL-ROUNDING-ON - shreax,16 ;put the fractional part in AX + mov eax,[bp+M1] + imul dword ptr [bp+M2] ;multiply +if MUL-ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) +adcedx,0 ;whole part of result is in DX +endif ;MUL-ROUNDING-ON +shreax,16 ;put the fractional part in AX - else;!USE386 +else;!USE386 - ;do four partial products and + ;do four partial products and + ; add them together, accumulating + ; the result in CX:BX + push si ;preserve C register variables + push di + ;figure out signs, so we can use + ; unsigned multiplies + sub cx,cx ;assume both operands positive + mov ax,word ptr [bp+M1+2] + mov si,word ptr [bp+M1] + and ax,ax ;first operand negative? + jns CheckSecondOperand ;no + neg ax ;yes, so negate first operand + neg si + sbb ax,0 + inc cx ;mark that first operand is negative +CheckSecondOperand: + mov bx,word ptr [bp+M2+2] + mov di,word ptr [bp+M2] + and bx,bx ;second operand negative? + jns SaveSignStatus ;no + neg bx ;yes, so negate second operand + neg di + sbb bx,0 + xor cx,1 ;mark that second operand is negative +SaveSignStatus: + push cx ;remember sign of result; 1 if result + ; negative, 0 if result nonnegative + push ax ;remember high word of M1 + mul bx ;high word M1 times high word M2 + mov cx,ax ;accumulate result in CX:BX (BX not used + ; until next operation, however) + ;assume no overflow into DX + mov ax,si ;low word M1 times high word M2 + mul bx + mov bx,ax + add cx,dx ;accumulate result in CX:BX + pop ax ;retrieve high word of M1 + mul di ;high word M1 times low word M2 + add bx,ax + adc cx,dx ;accumulate result in CX:BX + mov ax,si ;low word M1 times low word M2 +muldi +if MUL-ROUNDING-ON + add ax,8000h ;round by adding 2^(-17) +adcbx,dx +else ;!MUL-ROUNDING-ON + add bx,dx, ;don't round +endif ;MUL-ROUNDING-ON + adc cx,0 ;accumulate result in CX:BX + mov dx,cx + mov ax,bx + pop cx + and cx,cx ;is the result negative? + jz FixedMulDone ;no, we're all set + neg dx ;yes, so negate DX:AX + neg ax + sbb dx,0 +FixedMulDone: + + pop di ;restore C register variables + pop si + +endif;USE386 + + pop bp + ret +_FixedMul endp + +;===================================================================== +; Divides one fixed-point value by another. +; C near-callable as: +; Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor); +FDparms struc + dw 2 dup(?) ;return address & pushed BP +Dividend dd? +Divisor dd? +FDparms ends + alignALIGNMENT + public_FixedDiv +_FixedDivproc near + pushbp + movbp,sp + +if USE386 + +if DIV-ROUNDING-ON + sub cx,cx ;assume positive result + mov eax,[bp+Dividend] + and eax,eax ;positive dividend? + jns FDP1 ;yes + 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 in EAX + rol eax,16 ;put fractional part of dividend in + ; high word of EAX + mov dx,ax ;put whole part of dividend in DX + sub ax,ax ;clear low word of EAX + mov ebx,dword ptr [bp+Divisor] + and ebx,ebx ;positive divisor? + jns FDP2 ;yes + 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 + adc ebx,0 ; even + dec ebx + cmp ebx,edx ;set Carry if the remainder is at least + adc eax,0 ; half as large as the divisor, then + ; use that to round up if necessary + and cx,cx ;should the result be made negative? + jz FDP3 ;no + neg eax ;yes, negate it +FDP3: +else ;!DIV-ROUNDING-ON + mov edx,[bp+Dividend] + sub eax,eax + shrd eax,edx,16 ;position so that result ends up + sar edx,16 ; in EAX + idiv dword ptr [bp+Divisor] +endif ;DIV-ROUNDING-ON + shld edx,eax,16 ;whole part of result in DX; + ; fractional part is already in AX + +else ;!USE386 + + ;NOTE!!! Non-386 division uses a 32-bit dividend but only the upper 16 bits + ; of the divisor; in other words, only the integer part of the divisor is + ; used. This is done so that the division can be accomplished with two fast + ; hardware divides instead of a slow software implementation, and is (in my + ; opinion) acceptable because division is only used to project points to the + ; screen (normally, the divisor is a Z coordinate), so there's no cumulative + ; error, although there will be some error in pixel placement (the magnitude + ; of the error is less the farther away from the Z=0 plane objects are). This + ; is *not* a general-purpose divide, though; if the divisor is less than 1, + ; for instance, a divide-by-zero error will result! For this reason, non-386 + ; projection can't be performed for points closer to the viewpoint than Z=1. + ;figure out signs, so we can use + ; unsigned divisions + subcx, cx ;assume both operands positive + mov ax,word ptr [bp+Dividend+2] + and ax,ax;first operand negative? + jns CheckSecondOperandD ;no + neg ax ;yes, so negate first operand + neg word ptr [bp+Dividend] + sbbax,0 + inc cx ;mark that first operand is negative + CheckSecondOperandD: + mov bx,word ptr [bp+Divisor+2] + and bx,bx ;second operand negative? +jnsSaveSignStatusD;no + neg bx ;yes, so negate second operand + neg word ptr [bp+Divisor] + sbb bx,0 + xor cx,1 ;mark that second operand is negative +SaveSignStatusD: + push cx ;remember sign of result; 1 if result + ; negative, 0 if result nonnegative + sub dx,dx ;put Dividend+2 (integer part) in DX:AX + div bx ;first half of 32/16 division, integer part + ; divided by integer part + mov cx,ax ;set aside integer part of result + mov ax,word ptr [bp+Dividend] ;concatenate the fractional part of + ; the dividend to the remainder (fractional + ; part) of the result from dividing the + ; integer part of the dividend + div bx ;second half of 32/16 division + +if DIV-ROUNDING-ON EQ 0 + shr bx,1 ;divisor/2, minus 1 if the divisor is + adc bx,0 ; even + dec bx + cmp bx,dx ;set Carry if the remainder is at least + adc ax,0 ; half as large as the divisor, then + adc cx,0 ; use that to round up if necessary +endif ;DIV-ROUNDING-ON + + mov dx,cx ;absolute value of result in DX:AX + pop cx + and cx,cx ;is the result negative? + jz FixedDivDone ;no, we're all set + neg dx ;yes, so negate DX:AX + neg ax + sbb dx,0 +FixedDivDone: + +endif ;USE386 + + pop bp + ret +_FixedDiv endp + +;===================================================================== +; Returns the sine and cosine of an angle. +; C near-callable as: +; void CosSin(TAngle Angle, Fixedpoint *Cos, Fixedpoint *); + + alignALIGNMENT +CosTable label dword + include costable.inc + +SCparms struc + dw 2 dup(?) ;return address & pushed BP +Angle dw ? ;angle to calculate sine & cosine for +Cos dw ? ;pointer to cos destination +Sin dw ? ;pointer to sin destination +SCparms ends + + alignALIGNMENT + public _CosSin +_CosSin procnear + push bp ;preserve stack frame + mov bp,sp ;set up local stack frame + +if USE386 + + mov bx,[bp].Angle + 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 + js MakePos + jmp short CheckInRange + + align ALIGNMENT +MakeInRange: ;make sure angle is no more than 2*pi + sub bx,360*10 +CheckInRange: + cmp bx,360*10 + jg MakeInRange + + cmp bx,180*10 ;figure out which quadrant + ja BottomHalf ;quadrant 2 or 3 + cmp bx,90*10 ;quadrant 0 or 1 + ja Quadrant1 +;quadrant 0 + shl bx,2 + move ax,CosTable[bx] ;look up sine + neg bx;sin(Angle) = cos(90-Angle) + move dx,CosTable[bx+90*10*4] ;look up cosine + jmp short CSDone + + align ALIGNMENT +Quadrant1: + neg bx + add bx,180*10 ;convert to angle between 0 and 90 + shl bx,2 + move ax,CosTable[bx] ;look up cosine + neg eax ;negative in this quadrant + neg bx ;sin(Angle) = cos(90-Angle) + move dx,CosTable[bx+90*10*4] ;look up cosine + jmp short CSDone + + align ALIGNMENT +BottomHalf: ;quadrant 2 or 3 + neg bx + add bx,360*10 ;convert to angle between 0 and 180 + cmp bx,90*10 ;quadrant 2 or 3 + ja Quadrant2 + ;quadrant 3 + shl bx,2 + move ax,CosTable[bx] ;look up cosine + neg bx;sin(Angle) = cos(90-Angle) + move dx,CosTable[90*10*4+bx] ;look up sine + neg edx ;negative in this quadrant + jmp short CSDone + + align ALIGNMENT +Quadrant2: + neg bx + add bx,180*10 ;convert to angle between 0 and 90 + shl bx,2 + move ax,CosTable[bx] ;look up cosine + neg eax ;negative in this quadrant + neg bx ;sin(Angle) = cos(90-Angle) + move dx,CosTable[90*10*4+bx] ;look up sine + neg edx ;negative in this quadrant +CSDone: + mov bx,[bp].Cos + mov [bx],eax + mov bx,[bp].Sin + mov [bx],edx + +else ;!USE386 + + mov bx,[bp].Angle + 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 + js MakePos + jmp short CheckInRange + + align ALIGNMENT +MakeInRange: ; make sure angle is no more than 2*pi + sub bx,360*10 +CheckInRange: + cmp bx,360*10 + jg MakeInRange + + cmp bx,180*10 ;figure out which quadrant + ja BottomHalf ;quadrant 2 or 3 + cmp bx,90*10 ;quadrant 0 or 1 +jaQuadrant1 + ;quadrant 0 + shl bx,2 + mov ax,word ptr CosTable[bx] ;look up sine + mov dx,word ptr CosTable[bx+2] + neg bx ;sin(Angle) = cos(90-Angle) + mov cx,word ptr CosTable[bx+90*10*4+2] ;look up cosine + mov bx,word ptr CosTable[bx+90*10*4] + jmp CSDone + + align ALIGNMENT +Quadrant1: + neg bx + add bx,180*10 ;convert to angle between 0 and 90 + shl bx,2 + mov ax,word ptr CosTable[bx] ;look up cosine + mov dx,word ptr CosTable[bx+2] + neg dx ;negative in this quadrant + neg ax + sbb dx,0 + neg bx ;sin(Angle) = cos(90-Angle) + mov cx,word ptr CosTable[bx+90*10*4+2] ;look up cosine + mov bx,word ptr CosTable[bx+90*10*4] + jmp short CSDone + + align ALIGNMENT +BottomHalf: ;quadrant 2 or 3 + neg bx + add bx,360*10 ;convert to angle between 0 and 180 + cmp bx,90*10 ;quadrant 2 or 3 +jaQuadrant2 + ;quadrant 3 + shl bx,2 + mov ax,word ptr CosTable[bx] ;look up cosine + mov dx,word ptr CosTable[bx+2] + neg bx ;sin(Angle) = cos(90-Angle) + mov cx,word ptr CosTable[90*10*4+bx+2] ;look up sine + mov bx,word ptr CosTable[90*10*4+bx] + neg cx ;negative in this quadrant + neg bx + sbb cx,0 + jmp short CSDone + + align ALIGNMENT +Quadrant2: + neg bx + add bx,180*10 ;convert to angle between 0 and 90 + shl bx,2 + mov ax,word ptr CosTable[bx] ;look up cosine + mov dx,word ptr CosTable[bx+2] + neg dx ;negative in this quadrant + neg ax + sbb dx,0 + neg bx ;sin(Angle) = cos(90-Angle) + mov cx,word ptr CosTable[90*10*4+bx+2] ;look up sine + mov bx,word ptr CosTable[90*10*4+bx] + neg cx ;negative in this quadrant + neg bx + sbb cx,0 +CSDone: + push bx + mov bx,[bp].Cos + mov [bx],ax + mov [bx+2],dx + mov bx,[bp].Sin + pop ax + mov [bx],ax + mov [bx+2],cx + +endif ;USE386 + + pop bp ;restore stack frame + ret +_CosSin endp + +;===================================================================== +; 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 +; the W coordinate of the destination. +; C near-callable as: +; void XformVec(Xform WorkingXform, Fixedpoint *SourceVec, +; Fixedpoint *DestVec); +; +; This assembly code is equivalent to this C code: +; int i; +; +; for (i=0; i<3; i++) +; DestVec[i] = FixedMul(WorkingXform[i][0], SourceVec[0]) + +; FixedMul(WorkingXform[i][1], SourceVec[1]) + +; FixedMul(WorkingXform[i][2], SourceVec[2]) + +; WorkingXform[i][3]; /* no need to multiply by W = 1 */ + +XVparms struc + dw 2 dup(?) ;return address & pushed BP +WorkingXform dw ? ;pointer to transform matrix +SourceVec dw ? ;pointer to source vector +DestVec dw ? ;pointer to destination vector +XVparms ends + +; Macro for non-386 multiply. AX, BX, CX, DX destroyed. +FIXED-MUL MACRO M1,M2 + local CheckSecondOperand,SaveSignStatus,FixedMulDone + + ;do four partial products and ; add them together, accumulating ; the result in CX:BX - push si ;preserve C register variables - push di ;figure out signs, so we can use - ; unsigned multiplies - sub cx,cx ;assume both operands positive - mov ax,word ptr [bp+M1+2] - mov si,word ptr [bp+M1] - and ax,ax ;first operand negative? - jns CheckSecondOperand ;no - neg ax ;yes, so negate first operand - neg si - sbb ax,0 - inc cx ;mark that first operand is negative - CheckSecondOperand: - mov bx,word ptr [bp+M2+2] - mov di,word ptr [bp+M2] - and bx,bx ;second operand negative? - jns SaveSignStatus ;no - neg bx ;yes, so negate second operand - neg di - sbb bx,0 - xor cx,1 ;mark that second operand is negative - SaveSignStatus: - push cx ;remember sign of result; 1 if result + ; unsigned multiplies + sub cx,cx ;assume both operands positive + mov bx,word ptr [&M1&+2] + and bx,bx ;first operand negative? + jns CheckSecondOperand ;no + neg bx ;yes, so negate first operand + neg word ptr [&M1&] + sbb bx,0 + mov word ptr [&M1&+2],bx + inc cx ;mark that first operand is negative +CheckSecondOperand: + mov bx,word ptr [&M2&+2] + and bx,bx ;second operand negative? + jns SaveSignStatus ;no + neg bx ;yes, so negate second operand + neg word ptr [&M2&] + sbb bx,0 + mov word ptr [&M2&+2],bx + xor cx,1 ;mark that second operand is negative +SaveSignStatus: + push cx ;remember sign of result; 1 if result ; negative, 0 if result nonnegative - push ax ;remember high word of M1 - mul bx ;high word M1 times high word M2 - mov cx,ax ;accumulate result in CX:BX (BX not used - ; until next operation, however) + mov ax,word ptr [&M1&+2] ;high word times high word + mul word ptr [&M2&+2] + mov cx,ax ; ;assume no overflow into DX - mov ax,si ;low word M1 times high word M2 - mul bx - mov bx,ax - add cx,dx ;accumulate result in CX:BX - pop ax ;retrieve high word of M1 - mul di ;high word M1 times low word M2 - add bx,ax - adc cx,dx ;accumulate result in CX:BX - mov ax,si ;low word M1 times low word M2 - muldi - if MUL-ROUNDING-ON - add ax,8000h ;round by adding 2^(-17) - adcbx,dx - else ;!MUL-ROUNDING-ON - add bx,dx, ;don't round - endif ;MUL-ROUNDING-ON - adc cx,0 ;accumulate result in CX:BX - mov dx,cx - mov ax,bx - pop cx - and cx,cx ;is the result negative? - jz FixedMulDone ;no, we're all set - neg dx ;yes, so negate DX:AX - neg ax - sbb dx,0 - FixedMulDone: + mov ax,word ptr [&M1&+2] ;high word times low word + mul word ptr [&M2&] + mov bx,ax + add cx,dx + mov ax,word ptr [&M1&] ;low word times high word + mul word ptr [&M2&+2] + add bx,ax + adc cx,dx + mov ax,word ptr [&M1&] ;low word times low word + mul word ptr [&M2&] +if MUL-ROUNDING-ON + add ax,8000h ;round by adding 2^(-17) + adc bx,dx +else ;!MUL-ROUNDING-ON + add bx,dx ;don't round +endif ;MUL-ROUNDING-ON + adc cx,0 + mov dx,cx + mov ax,bx + pop cx + and cx,cx ;is the result negative? + jz FixedMulDone ;no, we're all set + neg dx ;yes, so negate DX:AX + neg ax + sbb dx,0 +FixedMulDone: + ENDM - pop di ;restore C register variables - pop si + align ALIGNMENT + public _XformVec +_XformVecprocnear + push bp ;preserve stack frame + mov bp,sp ;set up local stack frame + push si ;preserve register variables + push di - endif;USE386 +if USE386 - pop bp - ret - _FixedMul endp + mov si,[bp].WorkingXform ;SI points to xform matrix + mov bx,[bp].SourceVec ;BX points to source vector + mov di,[bp].DestVec ;DI points to dest vector - ;===================================================================== - ; Divides one fixed-point value by another. - ; C near-callable as: - ; Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor); - FDparms struc - dw 2 dup(?) ;return address & pushed BP - Dividend dd? - Divisor dd? - FDparms ends - alignALIGNMENT - public_FixedDiv - _FixedDivproc near - pushbp - movbp,sp +soff=0 +doff=0 + REPT 3 ;do once each for dest X, Y, and Z + mov eax,[si+soff] ;column 0 entry on this row + imul dword ptr [bx] ;xform entry times source X entry +if MUL-ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;MUL-ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + move cx,eax ;set running total - if USE386 + move ax,[si+soff+4] ;column 1 entry on this row + imul dword ptr [bx+4] ;xform entry times source Y entry +if MUL-ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;MUL-ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + add ecx,eax ;running total for this row - if DIV-ROUNDING-ON - sub cx,cx ;assume positive result - mov eax,[bp+Dividend] - and eax,eax ;positive dividend? - jns FDP1 ;yes - 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 in EAX - rol eax,16 ;put fractional part of dividend in - ; high word of EAX - mov dx,ax ;put whole part of dividend in DX - sub ax,ax ;clear low word of EAX - mov ebx,dword ptr [bp+Divisor] - and ebx,ebx ;positive divisor? - jns FDP2 ;yes - 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 - adc ebx,0 ; even - dec ebx - cmp ebx,edx ;set Carry if the remainder is at least - adc eax,0 ; half as large as the divisor, then - ; use that to round up if necessary - and cx,cx ;should the result be made negative? - jz FDP3 ;no - neg eax ;yes, negate it - FDP3: - else ;!DIV-ROUNDING-ON - mov edx,[bp+Dividend] - sub eax,eax - shrd eax,edx,16 ;position so that result ends up - sar edx,16 ; in EAX - idiv dword ptr [bp+Divisor] - endif ;DIV-ROUNDING-ON - shld edx,eax,16 ;whole part of result in DX; - ; fractional part is already in AX + move ax,[si+soff+8] ;column 2 entry on this row + imul dword ptr [bx+8] ;xform entry times source Z entry +if MUL-ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;MUL-ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + add ecx,eax ;running total for this row - else ;!USE386 + add ecx,[si+soff+12] ;add in translation + mov [di+doff],ecx ;save the result in the dest vector +soff=soff+16 +doff=doff+4 + ENDM - ;NOTE!!! Non-386 division uses a 32-bit dividend but only the upper 16 bits - ; of the divisor; in other words, only the integer part of the divisor is - ; used. This is done so that the division can be accomplished with two fast - ; hardware divides instead of a slow software implementation, and is (in my - ; opinion) acceptable because division is only used to project points to the - ; screen (normally, the divisor is a Z coordinate), so there's no cumulative - ; error, although there will be some error in pixel placement (the magnitude - ; of the error is less the farther away from the Z=0 plane objects are). This - ; is *not* a general-purpose divide, though; if the divisor is less than 1, - ; for instance, a divide-by-zero error will result! For this reason, non-386 - ; projection can't be performed for points closer to the viewpoint than Z=1. - ;figure out signs, so we can use - ; unsigned divisions - subcx, cx ;assume both operands positive - mov ax,word ptr [bp+Dividend+2] - and ax,ax;first operand negative? - jns CheckSecondOperandD ;no - neg ax ;yes, so negate first operand - neg word ptr [bp+Dividend] - sbbax,0 - inc cx ;mark that first operand is negative - CheckSecondOperandD: - mov bx,word ptr [bp+Divisor+2] - and bx,bx ;second operand negative? - jnsSaveSignStatusD;no - neg bx ;yes, so negate second operand - neg word ptr [bp+Divisor] - sbb bx,0 - xor cx,1 ;mark that second operand is negative - SaveSignStatusD: - push cx ;remember sign of result; 1 if result - ; negative, 0 if result nonnegative - sub dx,dx ;put Dividend+2 (integer part) in DX:AX - div bx ;first half of 32/16 division, integer part - ; divided by integer part - mov cx,ax ;set aside integer part of result - mov ax,word ptr [bp+Dividend] ;concatenate the fractional part of - ; the dividend to the remainder (fractional - ; part) of the result from dividing the - ; integer part of the dividend - div bx ;second half of 32/16 division +else ;!USE386 - if DIV-ROUNDING-ON EQ 0 - shr bx,1 ;divisor/2, minus 1 if the divisor is - adc bx,0 ; even - dec bx - cmp bx,dx ;set Carry if the remainder is at least - adc ax,0 ; half as large as the divisor, then - adc cx,0 ; use that to round up if necessary - endif ;DIV-ROUNDING-ON + mov si,[bp].WorkingXform ;SI points to xform matrix + mov di,[bp].SourceVec ;DI points to source vector + mov bx,[bp].DestVec ;BX points to dest vector + push bp ;preserve stack frame pointer - mov dx,cx ;absolute value of result in DX:AX - pop cx - and cx,cx ;is the result negative? - jz FixedDivDone ;no, we're all set - neg dx ;yes, so negate DX:AX - neg ax - sbb dx,0 - FixedDivDone: +soff=0 +doff=0 + REPT 3 ;do once each for dest X, Y, and Z + push bx ;remember dest vector pointer + push word ptr [si+soff+2] + push word ptr [si+soff] + push word ptr [di+2] + push word ptr [di] + call _FixedMul ;xform entry times source X entry + add sp,8;clear parameters from stack + mov cx,ax ;set running total + mov bp,dx - endif ;USE386 + push cx ;preserve low word of running total + push word ptr [si+soff+4+2] + push word ptr [si+soff+4] + push word ptr [di+4+2] + push word ptr [di+4] + call _FixedMul ;xform entry times source Y entry + add sp,8 ;clear parameters from stack + pop cx ;restore low word of running total + add cx,ax ;running total for this row + adc bp,dx - pop bp - ret - _FixedDiv endp + push cx ;preserve low word of running total + push word ptr [si+soff+8+2] + push word ptr [si+soff+8] + push word ptr [di+8+2] + push word ptr [di+8] + call _FixedMul ;xform entry times source Z entry + add sp,8 ;clear parameters from stack + pop cx ;restore low word of running total + add cx,ax ;running total for this row + adc bp,dx - ;===================================================================== - ; Returns the sine and cosine of an angle. - ; C near-callable as: - ; void CosSin(TAngle Angle, Fixedpoint *Cos, Fixedpoint *); + add cx,[si+soff+12] ;add in translation + adc bp,[si+soff+12+2] + pop bx ;restore dest vector pointer + mov [bx+doff],cx ;save the result in the dest vector + mov [bx+doff+2],bp +soff=soff+16 +doff=doff+4 + ENDM - alignALIGNMENT - CosTable label dword - include costable.inc + pop bp ;restore stack frame pointer - SCparms struc - dw 2 dup(?) ;return address & pushed BP - Angle dw ? ;angle to calculate sine & cosine for - Cos dw ? ;pointer to cos destination - Sin dw ? ;pointer to sin destination - SCparms ends +endif ;USE386 - alignALIGNMENT - public _CosSin - _CosSin procnear - push bp ;preserve stack frame - mov bp,sp ;set up local stack frame + pop di ;restore register variables + pop si + pop bp ;restore stack frame +ret +_XformVecendp - if USE386 +;===================================================================== +; 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 +; of the destination. +; C near-callable as: +; void ConcatXforms(Xform SourceXform1, Xform SourceXform2, +; Xform DestXform) +; +; This assembly code is equivalent to this C code: +; int i, j; +; +; for (i=0; i<3; i++) { +; for (j=0; j<3; j++) +; DestXform[i][j] = +; FixedMul(SourceXform1[i][0], SourceXform2[0][j]) + +; FixedMul(SourceXform1[i][1], SourceXform2[1][j]) + +; FixedMul(SourceXform1[i][2], SourceXform2[2][j]); +; DestXform[i][3] = +; FixedMul(SourceXform1[i][0], SourceXform2[0][3]) + +; FixedMul(SourceXform1[i][1], SourceXform2[1][3]) + +; FixedMul(SourceXform1[i][2], SourceXform2[2][3]) + +; SourceXform1[i][3]; +; } - mov bx,[bp].Angle - 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 - js MakePos - jmp short CheckInRange - - align ALIGNMENT - MakeInRange: ;make sure angle is no more than 2*pi - sub bx,360*10 - CheckInRange: - cmp bx,360*10 - jg MakeInRange - - cmp bx,180*10 ;figure out which quadrant - ja BottomHalf ;quadrant 2 or 3 - cmp bx,90*10 ;quadrant 0 or 1 - ja Quadrant1 - ;quadrant 0 - shl bx,2 - move ax,CosTable[bx] ;look up sine - neg bx;sin(Angle) = cos(90-Angle) - move dx,CosTable[bx+90*10*4] ;look up cosine - jmp short CSDone - - align ALIGNMENT - Quadrant1: - neg bx - add bx,180*10 ;convert to angle between 0 and 90 - shl bx,2 - move ax,CosTable[bx] ;look up cosine - neg eax ;negative in this quadrant - neg bx ;sin(Angle) = cos(90-Angle) - move dx,CosTable[bx+90*10*4] ;look up cosine - jmp short CSDone - - align ALIGNMENT - BottomHalf: ;quadrant 2 or 3 - neg bx - add bx,360*10 ;convert to angle between 0 and 180 - cmp bx,90*10 ;quadrant 2 or 3 - ja Quadrant2 - ;quadrant 3 - shl bx,2 - move ax,CosTable[bx] ;look up cosine - neg bx;sin(Angle) = cos(90-Angle) - move dx,CosTable[90*10*4+bx] ;look up sine - neg edx ;negative in this quadrant - jmp short CSDone - - align ALIGNMENT - Quadrant2: - neg bx - add bx,180*10 ;convert to angle between 0 and 90 - shl bx,2 - move ax,CosTable[bx] ;look up cosine - neg eax ;negative in this quadrant - neg bx ;sin(Angle) = cos(90-Angle) - move dx,CosTable[90*10*4+bx] ;look up sine - neg edx ;negative in this quadrant - CSDone: - mov bx,[bp].Cos - mov [bx],eax - mov bx,[bp].Sin - mov [bx],edx - - else ;!USE386 - - mov bx,[bp].Angle - 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 - js MakePos - jmp short CheckInRange +CXparms struc + dw 2 dup(?) ;return address & pushed BP +SourceXform1 dw ? ;pointer to first source xform matrix +SourceXform2 dw ? ;pointer to second source xform matrix +DestXform dw ? ;pointer to destination xform matrix +CXparms ends align ALIGNMENT - MakeInRange: ; make sure angle is no more than 2*pi - sub bx,360*10 - CheckInRange: - cmp bx,360*10 - jg MakeInRange + public _ConcatXforms +_ConcatXformsprocnear + push bp ;preserve stack frame + mov bp,sp ;set up local stack frame + push si ;preserve register variables + push di - cmp bx,180*10 ;figure out which quadrant - ja BottomHalf ;quadrant 2 or 3 - cmp bx,90*10 ;quadrant 0 or 1 - jaQuadrant1 - ;quadrant 0 - shl bx,2 - mov ax,word ptr CosTable[bx] ;look up sine - mov dx,word ptr CosTable[bx+2] - neg bx ;sin(Angle) = cos(90-Angle) - mov cx,word ptr CosTable[bx+90*10*4+2] ;look up cosine - mov bx,word ptr CosTable[bx+90*10*4] - jmp CSDone +if USE386 - align ALIGNMENT - Quadrant1: - neg bx - add bx,180*10 ;convert to angle between 0 and 90 - shl bx,2 - mov ax,word ptr CosTable[bx] ;look up cosine - mov dx,word ptr CosTable[bx+2] - neg dx ;negative in this quadrant - neg ax - sbb dx,0 - neg bx ;sin(Angle) = cos(90-Angle) - mov cx,word ptr CosTable[bx+90*10*4+2] ;look up cosine - mov bx,word ptr CosTable[bx+90*10*4] - jmp short CSDone + mov bx,[bp].SourceXform2 ;BX points to xform2 matrix + mov si,[bp].SourceXform1 ;SI points to xform1 matrix + mov di,[bp].DestXform ;DI points to dest xform matrix - align ALIGNMENT - BottomHalf: ;quadrant 2 or 3 - neg bx - add bx,360*10 ;convert to angle between 0 and 180 - cmp bx,90*10 ;quadrant 2 or 3 - jaQuadrant2 - ;quadrant 3 - shl bx,2 - mov ax,word ptr CosTable[bx] ;look up cosine - mov dx,word ptr CosTable[bx+2] - neg bx ;sin(Angle) = cos(90-Angle) - mov cx,word ptr CosTable[90*10*4+bx+2] ;look up sine - mov bx,word ptr CosTable[90*10*4+bx] - neg cx ;negative in this quadrant - neg bx - sbb cx,0 - jmp short CSDone +roff=0 ;row offset + REPT 3 ;once for each row +coff=0 ;column offset +REPT 3 ;once for each of the first 3 columns, + ; assuming 0 as the bottom entry (no + ; translation) + move ax,[si+roff] ;column 0 entry on this row + imul dword ptr [bx+coff] ;times row 0 entry in column +if MUL-ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;MUL-ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + move cx,eax ;set running total - align ALIGNMENT - Quadrant2: - neg bx - add bx,180*10 ;convert to angle between 0 and 90 - shl bx,2 - mov ax,word ptr CosTable[bx] ;look up cosine - mov dx,word ptr CosTable[bx+2] - neg dx ;negative in this quadrant - neg ax - sbb dx,0 - neg bx ;sin(Angle) = cos(90-Angle) - mov cx,word ptr CosTable[90*10*4+bx+2] ;look up sine - mov bx,word ptr CosTable[90*10*4+bx] - neg cx ;negative in this quadrant - neg bx - sbb cx,0 - CSDone: - push bx - mov bx,[bp].Cos - mov [bx],ax - mov [bx+2],dx - mov bx,[bp].Sin - pop ax - mov [bx],ax - mov [bx+2],cx + move ax,[si+roff+4] ;column 1 entry on this row + imuld word ptr [bx+coff+16] ;times row 1 entry in col +if MUL-ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;MUL-ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + add ecx,eax ;running total - endif ;USE386 + move ax,[si+roff+8] ;column 2 entry on this row + imuld word ptr [bx+coff+32] ;times row 2 entry in col +if MUL-ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;MUL-ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + add ecx,eax ;running total - pop bp ;restore stack frame - ret - _CosSin endp + mov [di+coff+roff],ecx ;save the result in dest matrix +coff=coff+4 ;point to next col in xform2 & dest + ENDM +;now do the fourth column, assuming +; 1 as the bottom entry, causing +; translation to be performed + move ax,[si+roff] ;column 0 entry on this row + imuld word ptr [bx+coff] ;times row 0 entry in column +if MUL-ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;MUL-ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + move cx,eax ;set running total - ;===================================================================== - ; 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 - ; the W coordinate of the destination. - ; C near-callable as: - ; void XformVec(Xform WorkingXform, Fixedpoint *SourceVec, - ; Fixedpoint *DestVec); - ; - ; This assembly code is equivalent to this C code: - ; int i; - ; - ; for (i=0; i<3; i++) - ; DestVec[i] = FixedMul(WorkingXform[i][0], SourceVec[0]) + - ; FixedMul(WorkingXform[i][1], SourceVec[1]) + - ; FixedMul(WorkingXform[i][2], SourceVec[2]) + - ; WorkingXform[i][3]; /* no need to multiply by W = 1 */ + move ax,[si+roff+4] ;column 1 entry on this row + imuld word ptr [bx+coff+16] ;times row 1 entry in col +if MUL-ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;MUL-ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + add ecx,eax ;running total - XVparms struc - dw 2 dup(?) ;return address & pushed BP - WorkingXform dw ? ;pointer to transform matrix - SourceVec dw ? ;pointer to source vector - DestVec dw ? ;pointer to destination vector - XVparms ends + move ax,[si+roff+8] ;column 2 entry on this row + imuld word ptr [bx+coff+32] ;times row 2 entry in col +if MUL-ROUNDING-ON + add eax,8000h ;round by adding 2^(-17) + adc edx,0 ;whole part of result is in DX +endif ;MUL-ROUNDING-ON + shrd eax,edx,16 ;shift the result back to 16.16 form + add ecx,eax ;running total - ; Macro for non-386 multiply. AX, BX, CX, DX destroyed. - FIXED-MUL MACRO M1,M2 - local CheckSecondOperand,SaveSignStatus,FixedMulDone + add ecx,[si+roff+12] ;add in translation - ;do four partial products and - ; add them together, accumulating - ; the result in CX:BX - ;figure out signs, so we can use - ; unsigned multiplies - sub cx,cx ;assume both operands positive - mov bx,word ptr [&M1&+2] - and bx,bx ;first operand negative? - jns CheckSecondOperand ;no - neg bx ;yes, so negate first operand - neg word ptr [&M1&] - sbb bx,0 - mov word ptr [&M1&+2],bx - inc cx ;mark that first operand is negative - CheckSecondOperand: - mov bx,word ptr [&M2&+2] - and bx,bx ;second operand negative? - jns SaveSignStatus ;no - neg bx ;yes, so negate second operand - neg word ptr [&M2&] - sbb bx,0 - mov word ptr [&M2&+2],bx - xor cx,1 ;mark that second operand is negative - SaveSignStatus: - push cx ;remember sign of result; 1 if result - ; negative, 0 if result nonnegative - mov ax,word ptr [&M1&+2] ;high word times high word - mul word ptr [&M2&+2] - mov cx,ax ; - ;assume no overflow into DX - mov ax,word ptr [&M1&+2] ;high word times low word - mul word ptr [&M2&] - mov bx,ax - add cx,dx - mov ax,word ptr [&M1&] ;low word times high word - mul word ptr [&M2&+2] - add bx,ax - adc cx,dx - mov ax,word ptr [&M1&] ;low word times low word - mul word ptr [&M2&] - if MUL-ROUNDING-ON - add ax,8000h ;round by adding 2^(-17) - adc bx,dx - else ;!MUL-ROUNDING-ON - add bx,dx ;don't round - endif ;MUL-ROUNDING-ON - adc cx,0 - mov dx,cx - mov ax,bx - pop cx - and cx,cx ;is the result negative? - jz FixedMulDone ;no, we're all set - neg dx ;yes, so negate DX:AX - neg ax - sbb dx,0 - FixedMulDone: - ENDM + mov [di+coff+roff],ecx ;save the result in dest matrix +coff=coff+4 ;point to next col in xform2 & dest - align ALIGNMENT - public _XformVec - _XformVecprocnear - push bp ;preserve stack frame - mov bp,sp ;set up local stack frame - push si ;preserve register variables - push di +roff=roff+16 ;point to next col in xform2 & dest + ENDM - if USE386 +else ;!USE386 - mov si,[bp].WorkingXform ;SI points to xform matrix - mov bx,[bp].SourceVec ;BX points to source vector - mov di,[bp].DestVec ;DI points to dest vector + mov di,[bp].SourceXform2 ;DI points to xform2 matrix + mov si,[bp].SourceXform1 ;SI points to xform1 matrix + mov bx,[bp].DestXform ;BX points to dest xform matrix + push bp ;preserve stack frame pointer - soff=0 - doff=0 - REPT 3 ;do once each for dest X, Y, and Z - mov eax,[si+soff] ;column 0 entry on this row - imul dword ptr [bx] ;xform entry times source X entry - if MUL-ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;MUL-ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - move cx,eax ;set running total +roff=0 ;row offset + REPT 3 ;once for each row +coff=0 ;column offset + REPT 3 ;once for each of the first 3 columns, +; assuming 0 as the bottom entry (no +; translation) + push bx ;remember dest vector pointer + push word ptr [si+roff+2] + push word ptr [si+roff] + push word ptr [di+coff+2] + push word ptr [di+coff] + call _FixedMul ;column 0 entry on this row times row 0 +; entry in column +addsp,8;clear parameters from stack + mov cx,ax ;set running total + mov bp,dx - move ax,[si+soff+4] ;column 1 entry on this row - imul dword ptr [bx+4] ;xform entry times source Y entry - if MUL-ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;MUL-ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - add ecx,eax ;running total for this row + push cx ;preserve low word of running total + push word ptr [si+roff+4+2] + push word ptr [si+roff+4] + push word ptr [di+coff+16+2] + push word ptr [di+coff+16] + call _FixedMul ;column 1 entry on this row times row 1 +; entry in column + add sp,8 ;clear parameters from stack + pop cx ;restore low word of running total + add cx,ax ;running total for this row + adc bp,dx - move ax,[si+soff+8] ;column 2 entry on this row - imul dword ptr [bx+8] ;xform entry times source Z entry - if MUL-ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;MUL-ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - add ecx,eax ;running total for this row + push cx ;preserve low word of running total + push word ptr [si+roff+8+2] + push word ptr [si+roff+8] + push word ptr [di+coff+32+2] + push word ptr [di+coff+32] + call _FixedMul ;column 1 entry on this row times row 1 +; entry in column + add sp,8 ;clear parameters from stack + pop cx ;restore low word of running total + add cx,ax ;running total for this row + adc bp,dx - add ecx,[si+soff+12] ;add in translation - mov [di+doff],ecx ;save the result in the dest vector - soff=soff+16 - doff=doff+4 - ENDM + pop bx ;restore DestXForm pointer + mov [bx+coff+roff],cx ;save the result in dest matrix + mov [bx+coff+roff+2],bp +coff=coff+4 ;point to next col in xform2 & dest + ENDM +;now do the fourth column, assuming +; 1 as the bottom entry, causing +; translation to be performed + push bx ;remember dest vector pointer + push word ptr [si+roff+2] + push word ptr [si+roff] + push word ptr [di+coff+2] + push word ptr [di+coff] + call _FixedMul ;column 0 entry on this row times row 0 +; entry in column + add sp,8 ;clear parameters from stack + mov cx,ax ;set running total + mov bp,dx - else ;!USE386 + push cx ;preserve low word of running total + push word ptr [si+roff+4+2] + push word ptr [si+roff+4] + push word ptr [di+coff+16+2] + push word ptr [di+coff+16] + call _FixedMul ;column 1 entry on this row times row 1 +; entry in column + add sp,8 ;clear parameters from stack + pop cx ;restore low word of running total + add cx,ax ;running total for this row + adc bp,dx - mov si,[bp].WorkingXform ;SI points to xform matrix - mov di,[bp].SourceVec ;DI points to source vector - mov bx,[bp].DestVec ;BX points to dest vector - push bp ;preserve stack frame pointer + push cx ;preserve low word of running total + push word ptr [si+roff+8+2] + push word ptr [si+roff+8] + push word ptr [di+coff+32+2] + push word ptr [di+coff+32] + call _FixedMul ;column 1 entry on this row times row 1 +; entry in column + add sp,8 ;clear parameters from stack + pop cx ;restore low word of running total + add cx,ax ;running total for this row + adc bp,dx - soff=0 - doff=0 - REPT 3 ;do once each for dest X, Y, and Z - push bx ;remember dest vector pointer - push word ptr [si+soff+2] - push word ptr [si+soff] - push word ptr [di+2] - push word ptr [di] - call _FixedMul ;xform entry times source X entry - add sp,8;clear parameters from stack - mov cx,ax ;set running total - mov bp,dx + add cx,[si+roff+12] ;add in translation + add bp,[si+roff+12+2] - push cx ;preserve low word of running total - push word ptr [si+soff+4+2] - push word ptr [si+soff+4] - push word ptr [di+4+2] - push word ptr [di+4] - call _FixedMul ;xform entry times source Y entry - add sp,8 ;clear parameters from stack - pop cx ;restore low word of running total - add cx,ax ;running total for this row - adc bp,dx + pop bx ;restore DestXForm pointer + mov [bx+coff+roff],cx ;save the result in dest matrix + mov [bx+coff+roff+2],bp +coff=coff+4 ;point to next col in xform2 & dest - push cx ;preserve low word of running total - push word ptr [si+soff+8+2] - push word ptr [si+soff+8] - push word ptr [di+8+2] - push word ptr [di+8] - call _FixedMul ;xform entry times source Z entry - add sp,8 ;clear parameters from stack - pop cx ;restore low word of running total - add cx,ax ;running total for this row - adc bp,dx +roff=roff+16 ;point to next col in xform2 & dest + ENDM - add cx,[si+soff+12] ;add in translation - adc bp,[si+soff+12+2] - pop bx ;restore dest vector pointer - mov [bx+doff],cx ;save the result in the dest vector - mov [bx+doff+2],bp - soff=soff+16 - doff=doff+4 - ENDM + pop bp ;restore stack frame pointer - pop bp ;restore stack frame pointer +endif ;USE386 - endif ;USE386 - - pop di ;restore register variables - pop si - pop bp ;restore stack frame - ret - _XformVecendp - - ;===================================================================== - ; 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 - ; of the destination. - ; C near-callable as: - ; void ConcatXforms(Xform SourceXform1, Xform SourceXform2, - ; Xform DestXform) - ; - ; This assembly code is equivalent to this C code: - ; int i, j; - ; - ; for (i=0; i<3; i++) { - ; for (j=0; j<3; j++) - ; DestXform[i][j] = - ; FixedMul(SourceXform1[i][0], SourceXform2[0][j]) + - ; FixedMul(SourceXform1[i][1], SourceXform2[1][j]) + - ; FixedMul(SourceXform1[i][2], SourceXform2[2][j]); - ; DestXform[i][3] = - ; FixedMul(SourceXform1[i][0], SourceXform2[0][3]) + - ; FixedMul(SourceXform1[i][1], SourceXform2[1][3]) + - ; FixedMul(SourceXform1[i][2], SourceXform2[2][3]) + - ; SourceXform1[i][3]; - ; } - - CXparms struc - dw 2 dup(?) ;return address & pushed BP - SourceXform1 dw ? ;pointer to first source xform matrix - SourceXform2 dw ? ;pointer to second source xform matrix - DestXform dw ? ;pointer to destination xform matrix - CXparms ends - - align ALIGNMENT - public _ConcatXforms - _ConcatXformsprocnear - push bp ;preserve stack frame - mov bp,sp ;set up local stack frame - push si ;preserve register variables - push di - - if USE386 - - mov bx,[bp].SourceXform2 ;BX points to xform2 matrix - mov si,[bp].SourceXform1 ;SI points to xform1 matrix - mov di,[bp].DestXform ;DI points to dest xform matrix - - roff=0 ;row offset - REPT 3 ;once for each row - coff=0 ;column offset - REPT 3 ;once for each of the first 3 columns, - ; assuming 0 as the bottom entry (no - ; translation) - move ax,[si+roff] ;column 0 entry on this row - imul dword ptr [bx+coff] ;times row 0 entry in column - if MUL-ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;MUL-ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - move cx,eax ;set running total - - move ax,[si+roff+4] ;column 1 entry on this row - imuld word ptr [bx+coff+16] ;times row 1 entry in col - if MUL-ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;MUL-ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - add ecx,eax ;running total - - move ax,[si+roff+8] ;column 2 entry on this row - imuld word ptr [bx+coff+32] ;times row 2 entry in col - if MUL-ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;MUL-ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - add ecx,eax ;running total - - mov [di+coff+roff],ecx ;save the result in dest matrix - coff=coff+4 ;point to next col in xform2 & dest - ENDM - ;now do the fourth column, assuming - ; 1 as the bottom entry, causing - ; translation to be performed - move ax,[si+roff] ;column 0 entry on this row - imuld word ptr [bx+coff] ;times row 0 entry in column - if MUL-ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;MUL-ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - move cx,eax ;set running total - - move ax,[si+roff+4] ;column 1 entry on this row - imuld word ptr [bx+coff+16] ;times row 1 entry in col - if MUL-ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;MUL-ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - add ecx,eax ;running total - - move ax,[si+roff+8] ;column 2 entry on this row - imuld word ptr [bx+coff+32] ;times row 2 entry in col - if MUL-ROUNDING-ON - add eax,8000h ;round by adding 2^(-17) - adc edx,0 ;whole part of result is in DX - endif ;MUL-ROUNDING-ON - shrd eax,edx,16 ;shift the result back to 16.16 form - add ecx,eax ;running total - - add ecx,[si+roff+12] ;add in translation - - mov [di+coff+roff],ecx ;save the result in dest matrix - coff=coff+4 ;point to next col in xform2 & dest - - roff=roff+16 ;point to next col in xform2 & dest - ENDM - - else ;!USE386 - - mov di,[bp].SourceXform2 ;DI points to xform2 matrix - mov si,[bp].SourceXform1 ;SI points to xform1 matrix - mov bx,[bp].DestXform ;BX points to dest xform matrix - push bp ;preserve stack frame pointer - - roff=0 ;row offset - REPT 3 ;once for each row - coff=0 ;column offset - REPT 3 ;once for each of the first 3 columns, - ; assuming 0 as the bottom entry (no - ; translation) - push bx ;remember dest vector pointer - push word ptr [si+roff+2] - push word ptr [si+roff] - push word ptr [di+coff+2] - push word ptr [di+coff] - call _FixedMul ;column 0 entry on this row times row 0 - ; entry in column - addsp,8;clear parameters from stack - mov cx,ax ;set running total - mov bp,dx - - push cx ;preserve low word of running total - push word ptr [si+roff+4+2] - push word ptr [si+roff+4] - push word ptr [di+coff+16+2] - push word ptr [di+coff+16] - call _FixedMul ;column 1 entry on this row times row 1 - ; entry in column - add sp,8 ;clear parameters from stack - pop cx ;restore low word of running total - add cx,ax ;running total for this row - adc bp,dx - - push cx ;preserve low word of running total - push word ptr [si+roff+8+2] - push word ptr [si+roff+8] - push word ptr [di+coff+32+2] - push word ptr [di+coff+32] - call _FixedMul ;column 1 entry on this row times row 1 - ; entry in column - add sp,8 ;clear parameters from stack - pop cx ;restore low word of running total - add cx,ax ;running total for this row - adc bp,dx - - pop bx ;restore DestXForm pointer - mov [bx+coff+roff],cx ;save the result in dest matrix - mov [bx+coff+roff+2],bp - coff=coff+4 ;point to next col in xform2 & dest - ENDM - ;now do the fourth column, assuming - ; 1 as the bottom entry, causing - ; translation to be performed - push bx ;remember dest vector pointer - push word ptr [si+roff+2] - push word ptr [si+roff] - push word ptr [di+coff+2] - push word ptr [di+coff] - call _FixedMul ;column 0 entry on this row times row 0 - ; entry in column - add sp,8 ;clear parameters from stack - mov cx,ax ;set running total - mov bp,dx - - push cx ;preserve low word of running total - push word ptr [si+roff+4+2] - push word ptr [si+roff+4] - push word ptr [di+coff+16+2] - push word ptr [di+coff+16] - call _FixedMul ;column 1 entry on this row times row 1 - ; entry in column - add sp,8 ;clear parameters from stack - pop cx ;restore low word of running total - add cx,ax ;running total for this row - adc bp,dx - - push cx ;preserve low word of running total - push word ptr [si+roff+8+2] - push word ptr [si+roff+8] - push word ptr [di+coff+32+2] - push word ptr [di+coff+32] - call _FixedMul ;column 1 entry on this row times row 1 - ; entry in column - add sp,8 ;clear parameters from stack - pop cx ;restore low word of running total - add cx,ax ;running total for this row - adc bp,dx - - add cx,[si+roff+12] ;add in translation - add bp,[si+roff+12+2] - - pop bx ;restore DestXForm pointer - mov [bx+coff+roff],cx ;save the result in dest matrix - mov [bx+coff+roff+2],bp - coff=coff+4 ;point to next col in xform2 & dest - - roff=roff+16 ;point to next col in xform2 & dest - ENDM - - pop bp ;restore stack frame pointer - - endif ;USE386 - - pop di ;restore register variables - pop si - pop bp ;restore stack frame - ret - _ConcatXforms endp - end + pop di ;restore register variables + pop si + pop bp ;restore stack frame +ret +_ConcatXforms endp +end +``` diff --git a/54-04.md b/54-04.md index f866124..2a01251 100644 --- a/54-04.md +++ b/54-04.md @@ -12,129 +12,131 @@ pages: 1025-1027 **LISTING 54.2 DRAWPOBJ.C** - /* Draws all visible faces in the specified polygon-based object. The object - must have previously been transformed and projected, so that all vertex - arrays are filled in. Ambient and diffuse shading are supported. */ - #include "polygon.h" +```c +/* Draws all visible faces in the specified polygon-based object. The object + must have previously been transformed and projected, so that all vertex + arrays are filled in. Ambient and diffuse shading are supported. */ +#include "polygon.h" - void DrawPObject(PObject * ObjectToXform) - { - int i, j, NumFaces = ObjectToXform->NumFaces, NumVertices; - int * VertNumsPtr, Spot; - Face * FacePtr = ObjectToXform->FaceList; - Point * ScreenPoints = ObjectToXform->ScreenVertexList; - PointListHeader Polygon; - Fixedpoint Diffusion; - ModelColor ColorTemp; - ModelIntensity IntensityTemp; - Point3 UnitNormal, *NormalStartpoint, *NormalEndpoint; - long v1, v2, w1, w2; - Point Vertices[MAX-POLY-LENGTH]; +void DrawPObject(PObject * ObjectToXform) +{ + int i, j, NumFaces = ObjectToXform->NumFaces, NumVertices; + int * VertNumsPtr, Spot; + Face * FacePtr = ObjectToXform->FaceList; + Point * ScreenPoints = ObjectToXform->ScreenVertexList; + PointListHeader Polygon; + Fixedpoint Diffusion; + ModelColor ColorTemp; + ModelIntensity IntensityTemp; + Point3 UnitNormal, *NormalStartpoint, *NormalEndpoint; + long v1, v2, w1, w2; + Point Vertices[MAX-POLY-LENGTH]; - /* Draw each visible face (polygon) of the object in turn */ - for (i=0; iVertNums; - NormalEndpoint = &ObjectToXform->XformedVertexList[*VertNumsPtr++]; - NormalStartpoint = &ObjectToXform->XformedVertexList[*VertNumsPtr]; - /* Copy over the face's vertices from the vertex list */ - NumVertices = FacePtr->NumVerts; - for (j=0; j 0) { - /* It is facing the screen, so draw */ - /* Appropriately adjust the extent of the rectangle used to - erase this object later */ - for (j=0; j - ObjectToXform->EraseRect[NonDisplayedPage].Right) - if (Vertices[j].X < SCREEN-WIDTH) - ObjectToXform->EraseRect[NonDisplayedPage].Right = - Vertices[j].X; - else ObjectToXform->EraseRect[NonDisplayedPage].Right = - SCREEN-WIDTH; - if (Vertices[j].Y > - ObjectToXform->EraseRect[NonDisplayedPage].Bottom) - if (Vertices[j].Y < SCREEN-HEIGHT) - ObjectToXform->EraseRect[NonDisplayedPage].Bottom = - Vertices[j].Y; - else ObjectToXform->EraseRect[NonDisplayedPage].Bottom= - SCREEN-HEIGHT; - if (Vertices[j].X < - ObjectToXform->EraseRect[NonDisplayedPage].Left) - if (Vertices[j].X > 0) - ObjectToXform->EraseRect[NonDisplayedPage].Left = - Vertices[j].X; - else ObjectToXform->EraseRect[NonDisplayedPage].Left=0; - if (Vertices[j].Y < - ObjectToXform->EraseRect[NonDisplayedPage].Top) - if (Vertices[j].Y > 0) - ObjectToXform->EraseRect[NonDisplayedPage].Top = - Vertices[j].Y; - else ObjectToXform->EraseRect[NonDisplayedPage].Top=0; - } - /* See if there's any shading */ - if (FacePtr->ShadingType == 0) { - /* No shading in effect, so just draw */ - DRAW-POLYGON(Vertices, NumVertices, FacePtr->ColorIndex, 0, 0); - } else { - /* Handle shading */ - /* Do ambient shading, if enabled */ - if (AmbientOn && (FacePtr->ShadingType & AMBIENT-SHADING)) { - /* Use the ambient shading component */ - IntensityTemp = AmbientIntensity; - } else { - SET-INTENSITY(IntensityTemp, 0, 0, 0); - } - /* Do diffuse shading, if enabled */ - if (FacePtr->ShadingType & DIFFUSE-SHADING) { - /* Calculate the unit normal for this polygon, for use in dot - products */ - UnitNormal.X = NormalEndpoint->X - NormalStartpoint->X; - UnitNormal.Y = NormalEndpoint->Y - NormalStartpoint->Y; - UnitNormal.Z = NormalEndpoint->Z - NormalStartpoint->Z; - /* Calculate the diffuse shading component for each active - spotlight */ - for (Spot=0; Spot 0) { - IntensityTemp.Red += - FixedMul(SpotIntensity[Spot].Red, Diffusion); - IntensityTemp.Green += - FixedMul(SpotIntensity[Spot].Green, Diffusion); - IntensityTemp.Blue += - FixedMul(SpotIntensity[Spot].Blue, Diffusion); - } - } - } - } - /* Convert the drawing color to the desired fraction of the - brightest possible color */ - IntensityAdjustColor(&ColorTemp, &FacePtr->FullColor, - &IntensityTemp); - /* Draw with the cumulative shading, converting from the general - color representation to the best-match color index */ - DRAW-POLYGON(Vertices, NumVertices, - ModelColorToColorIndex(&ColorTemp), 0, 0); - } - } - } - } + /* Draw each visible face (polygon) of the object in turn */ + for (i=0; iVertNums; + NormalEndpoint = &ObjectToXform->XformedVertexList[*VertNumsPtr++]; + NormalStartpoint = &ObjectToXform->XformedVertexList[*VertNumsPtr]; + /* Copy over the face's vertices from the vertex list */ + NumVertices = FacePtr->NumVerts; + for (j=0; j 0) { + /* It is facing the screen, so draw */ + /* Appropriately adjust the extent of the rectangle used to + erase this object later */ + for (j=0; j + ObjectToXform->EraseRect[NonDisplayedPage].Right) + if (Vertices[j].X < SCREEN-WIDTH) + ObjectToXform->EraseRect[NonDisplayedPage].Right = + Vertices[j].X; + else ObjectToXform->EraseRect[NonDisplayedPage].Right = + SCREEN-WIDTH; + if (Vertices[j].Y > + ObjectToXform->EraseRect[NonDisplayedPage].Bottom) + if (Vertices[j].Y < SCREEN-HEIGHT) + ObjectToXform->EraseRect[NonDisplayedPage].Bottom = + Vertices[j].Y; + else ObjectToXform->EraseRect[NonDisplayedPage].Bottom= + SCREEN-HEIGHT; + if (Vertices[j].X < + ObjectToXform->EraseRect[NonDisplayedPage].Left) + if (Vertices[j].X > 0) + ObjectToXform->EraseRect[NonDisplayedPage].Left = + Vertices[j].X; + else ObjectToXform->EraseRect[NonDisplayedPage].Left=0; + if (Vertices[j].Y < + ObjectToXform->EraseRect[NonDisplayedPage].Top) + if (Vertices[j].Y > 0) + ObjectToXform->EraseRect[NonDisplayedPage].Top = + Vertices[j].Y; + else ObjectToXform->EraseRect[NonDisplayedPage].Top=0; + } + /* See if there's any shading */ + if (FacePtr->ShadingType == 0) { + /* No shading in effect, so just draw */ + DRAW-POLYGON(Vertices, NumVertices, FacePtr->ColorIndex, 0, 0); + } else { + /* Handle shading */ + /* Do ambient shading, if enabled */ + if (AmbientOn && (FacePtr->ShadingType & AMBIENT-SHADING)) { + /* Use the ambient shading component */ + IntensityTemp = AmbientIntensity; + } else { + SET-INTENSITY(IntensityTemp, 0, 0, 0); + } + /* Do diffuse shading, if enabled */ + if (FacePtr->ShadingType & DIFFUSE-SHADING) { + /* Calculate the unit normal for this polygon, for use in dot + products */ + UnitNormal.X = NormalEndpoint->X - NormalStartpoint->X; + UnitNormal.Y = NormalEndpoint->Y - NormalStartpoint->Y; + UnitNormal.Z = NormalEndpoint->Z - NormalStartpoint->Z; + /* Calculate the diffuse shading component for each active + spotlight */ + for (Spot=0; Spot 0) { + IntensityTemp.Red += + FixedMul(SpotIntensity[Spot].Red, Diffusion); + IntensityTemp.Green += + FixedMul(SpotIntensity[Spot].Green, Diffusion); + IntensityTemp.Blue += + FixedMul(SpotIntensity[Spot].Blue, Diffusion); + } + } + } + } + /* Convert the drawing color to the desired fraction of the + brightest possible color */ + IntensityAdjustColor(&ColorTemp, &FacePtr->FullColor, + &IntensityTemp); + /* Draw with the cumulative shading, converting from the general + color representation to the best-match color index */ + DRAW-POLYGON(Vertices, NumVertices, + ModelColorToColorIndex(&ColorTemp), 0, 0); + } + } + } +} +``` diff --git a/55-01.md b/55-01.md index 288afec..14120b7 100644 --- a/55-01.md +++ b/55-01.md @@ -91,11 +91,13 @@ blue resolution, using the structure shown in Listing 55.1. **LISTING 55.1 L55-1.C** - typedef struct -ModelColor { - unsigned char Red; /* 255 = max red, 0 = no red */ - unsigned char Green; /* 255 = max green, 0 = no green */ - unsigned char Blue; /* 255 = max blue, 0 = no blue */ - } ModelColor; +```c +typedef struct -ModelColor { + unsigned char Red; /* 255 = max red, 0 = no red */ + unsigned char Green; /* 255 = max green, 0 = no green */ + unsigned char Blue; /* 255 = max blue, 0 = no blue */ +} ModelColor; +``` Here, each color is described by three color components—one each for red, green, and blue—and each primary color component is represented by diff --git a/55-02.md b/55-02.md index 5750426..9b86e7d 100644 --- a/55-02.md +++ b/55-02.md @@ -46,88 +46,90 @@ that set up and perform the color mapping for DEMO1. **LISTING 55.2 L55-2.C** - /* Sets up the palette in mode X, to a 2-2-2 general R-G-B organization, with - 64 separate levels each of pure red, green, and blue. This is very good - for pure colors, but mediocre at best for mixes. +```c +/* Sets up the palette in mode X, to a 2-2-2 general R-G-B organization, with + 64 separate levels each of pure red, green, and blue. This is very good + for pure colors, but mediocre at best for mixes. - ------------------------ - |0 0 | Red|Green| Blue | - ------------------------ - 7 6 5 4 3 2 1 0 + ------------------------ + |0 0 | Red|Green| Blue | + ------------------------ + 7 6 5 4 3 2 1 0 - ------------------------ - |0 1 | Red | - ------------------------ - 7 6 5 4 3 2 1 0 + ------------------------ + |0 1 | Red | + ------------------------ + 7 6 5 4 3 2 1 0 - ------------------------ - |1 0 | Green | - ------------------------ - 7 6 5 4 3 2 1 0 + ------------------------ + |1 0 | Green | + ------------------------ + 7 6 5 4 3 2 1 0 - ------------------------ - |1 1 | Blue | - ------------------------ - 7 6 5 4 3 2 1 0 + ------------------------ + |1 1 | Blue | + ------------------------ + 7 6 5 4 3 2 1 0 - Colors are gamma corrected for a gamma of 2.3 to provide approximately - even intensity steps on the screen. - */ + Colors are gamma corrected for a gamma of 2.3 to provide approximately + even intensity steps on the screen. +*/ - #include - #include "polygon.h" +#include +#include "polygon.h" - static unsigned char Gamma4Levels[] = { 0, 39, 53, 63 }; - static unsigned char Gamma64Levels[] = { - 0, 10, 14, 17, 19, 21, 23, 24, 26, 27, 28, 29, 31, 32, 33, 34, - 35, 36, 37, 37, 38, 39, 40, 41, 41, 42, 43, 44, 44, 45, 46, 46, - 47, 48, 48, 49, 49, 50, 51, 51, 52, 52, 53, 53, 54, 54, 55, 55, - 56, 56, 57, 57, 58, 58, 59, 59, 60, 60, 61, 61, 62, 62, 63, 63, - }; +static unsigned char Gamma4Levels[] = { 0, 39, 53, 63 }; +static unsigned char Gamma64Levels[] = { + 0, 10, 14, 17, 19, 21, 23, 24, 26, 27, 28, 29, 31, 32, 33, 34, + 35, 36, 37, 37, 38, 39, 40, 41, 41, 42, 43, 44, 44, 45, 46, 46, + 47, 48, 48, 49, 49, 50, 51, 51, 52, 52, 53, 53, 54, 54, 55, 55, + 56, 56, 57, 57, 58, 58, 59, 59, 60, 60, 61, 61, 62, 62, 63, 63, +}; - static unsigned char PaletteBlock[256][3]; /* 256 RGB entries */ +static unsigned char PaletteBlock[256][3]; /* 256 RGB entries */ - void InitializePalette() - { - int Red, Green, Blue, Index; - union REGS regset; - struct SREGS sregset; +void InitializePalette() +{ + int Red, Green, Blue, Index; + union REGS regset; + struct SREGS sregset; - for (Red=0; Red<4; Red++) { - for (Green=0; Green<4; Green++) { - for (Blue=0; Blue<4; Blue++) { - Index = (Red<<4)+(Green<<2)+Blue; - PaletteBlock[Index][0] = Gamma4Levels[Red]; - PaletteBlock[Index][1] = Gamma4Levels[Green]; - PaletteBlock[Index][2] = Gamma4Levels[Blue]; - } - } + for (Red=0; Red<4; Red++) { + for (Green=0; Green<4; Green++) { + for (Blue=0; Blue<4; Blue++) { + Index = (Red<<4)+(Green<<2)+Blue; + PaletteBlock[Index][0] = Gamma4Levels[Red]; + PaletteBlock[Index][1] = Gamma4Levels[Green]; + PaletteBlock[Index][2] = Gamma4Levels[Blue]; } - - for (Red=0; Red<64; Red++) { - PaletteBlock[64+Red][0] = Gamma64Levels[Red]; - PaletteBlock[64+Red][1] = 0; - PaletteBlock[64+Red][2] = 0; - } - - for (Green=0; Green<64; Green++) { - PaletteBlock[128+Green][0] = 0; - PaletteBlock[128+Green][1] = Gamma64Levels[Green]; - PaletteBlock[128+Green][2] = 0; - } - - for (Blue=0; Blue<64; Blue++) { - PaletteBlock[192+Blue][0] = 0; - PaletteBlock[192+Blue][1] = 0; - PaletteBlock[192+Blue][2] = Gamma64Levels[Blue]; - } - - /* Now set up the palette */ - regset.x.ax = 0x1012; /* set block of DAC registers function */ - regset.x.bx = 0; /* first DAC location to load */ - regset.x.cx = 256; /* # of DAC locations to load */ - regset.x.dx = (unsigned int)PaletteBlock; /* offset of array from which - to load RGB settings */ - sregset.es = DS; /* segment of array from which to load settings */ - int86x(0x10, ®set, ®set, &sregset); /* load the palette block */ } + } + + for (Red=0; Red<64; Red++) { + PaletteBlock[64+Red][0] = Gamma64Levels[Red]; + PaletteBlock[64+Red][1] = 0; + PaletteBlock[64+Red][2] = 0; + } + + for (Green=0; Green<64; Green++) { + PaletteBlock[128+Green][0] = 0; + PaletteBlock[128+Green][1] = Gamma64Levels[Green]; + PaletteBlock[128+Green][2] = 0; + } + + for (Blue=0; Blue<64; Blue++) { + PaletteBlock[192+Blue][0] = 0; + PaletteBlock[192+Blue][1] = 0; + PaletteBlock[192+Blue][2] = Gamma64Levels[Blue]; + } + + /* Now set up the palette */ + regset.x.ax = 0x1012; /* set block of DAC registers function */ + regset.x.bx = 0; /* first DAC location to load */ + regset.x.cx = 256; /* # of DAC locations to load */ + regset.x.dx = (unsigned int)PaletteBlock; /* offset of array from which + to load RGB settings */ + sregset.es = DS; /* segment of array from which to load settings */ + int86x(0x10, ®set, ®set, &sregset); /* load the palette block */ +} +``` diff --git a/55-03.md b/55-03.md index 9a1cec1..eaed444 100644 --- a/55-03.md +++ b/55-03.md @@ -12,28 +12,30 @@ pages: 1038-1040 **LISTING 55.3 L55-3.C** - /* Converts a model color (a color in the RGB color cube, in the current - color model) to a color index for mode X. Pure primary colors are - special-cased, and everything else is handled by a 2-2-2 model. */ - int ModelColorToColorIndex(ModelColor * Color) - { - if (Color->Red == 0) { - if (Color->Green == 0) { - /* Pure blue */ - return(192+(Color->Blue >> 2)); - } else if (Color->Blue == 0) { - /* Pure green */ - return(128+(Color->Green >> 2)); - } - } else if ((Color->Green == 0) && (Color->Blue == 0)) { - /* Pure red */ - return(64+(Color->Red >> 2)); - } - /* Multi-color mix; look up the index with the two most significant bits - of each color component */ - return(((Color->Red & 0xC0) >> 2) | ((Color->Green & 0xC0) >> 4) | - ((Color->Blue & 0xC0) >> 6)); +```c +/* Converts a model color (a color in the RGB color cube, in the current + color model) to a color index for mode X. Pure primary colors are + special-cased, and everything else is handled by a 2-2-2 model. */ +int ModelColorToColorIndex(ModelColor * Color) +{ + if (Color->Red == 0) { + if (Color->Green == 0) { + /* Pure blue */ + return(192+(Color->Blue >> 2)); + } else if (Color->Blue == 0) { + /* Pure green */ + return(128+(Color->Green >> 2)); } + } else if ((Color->Green == 0) && (Color->Blue == 0)) { + /* Pure red */ + return(64+(Color->Red >> 2)); + } + /* Multi-color mix; look up the index with the two most significant bits + of each color component */ + return(((Color->Red & 0xC0) >> 2) | ((Color->Green & 0xC0) >> 4) | + ((Color->Blue & 0xC0) >> 6)); +} +``` In DEMO1, three-quarters of the palette is set up with 64 intensity levels of each of the three pure primary colors (red, green, and blue), diff --git a/55-04.md b/55-04.md index cdc8834..c1338bd 100644 --- a/55-04.md +++ b/55-04.md @@ -50,172 +50,174 @@ 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 - ; 3-based, one-pass technique. +```nasm + ; 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) - SCREEN_HEIGHT equ 480 ;# of scan lines per screen - SCREEN_SEGMENT equ 0a000h ;where screen memory is - FG_COLOR equ 14 ;text color - BG_COLOR equ 1 ;background box color - GC_INDEX equ 3ceh ;Graphics Controller (GC) Index reg I/O port - SET_RESET equ 0 ;Set/Reset register index in GC - G_MODE equ 5 ;Graphics Mode register index in GC - BIT_MASK equ 8 ;Bit Mask register index in GC + CHAR_HEIGHT equ 8 ;# of scan lines per character (must be <256) + SCREEN_HEIGHT equ 480 ;# of scan lines per screen + SCREEN_SEGMENT equ 0a000h ;where screen memory is + FG_COLOR equ 14 ;text color + BG_COLOR equ 1 ;background box color + GC_INDEX equ 3ceh ;Graphics Controller (GC) Index reg I/O port + SET_RESET equ 0 ;Set/Reset register index in GC + G_MODE equ 5 ;Graphics Mode register index in GC + BIT_MASK equ 8 ;Bit Mask register index in GC - .model small - .stack 200h - .data - Line dw ? ;current line # - CharHeight dw ? ;# of scan lines in each character (must be <256) - MaxLines dw ? ;max # of scan lines of text that will fit on screen - 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 + .model small + .stack 200h + .data + Line dw ? ;current line # + CharHeight dw ? ;# of scan lines in each character (must be <256) + MaxLines dw ? ;max # of scan lines of text that will fit on screen + 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 - .code - start: - mov ax,@data - mov ds,ax + .code + start: + mov ax,@data + mov ds,ax - mov ax,12h - int 10h ;select 640x480 16-color mode + mov ax,12h + int 10h ;select 640x480 16-color mode - mov ah,11h ;BIOS character generator function - mov al,30h ;BIOS get font pointer subfunction - mov bh,3 ;get 8x8 ROM font subsubfunction - int 10h ;get the pointer to the BIOS 8x8 font - mov word ptr [FontPtr],bp - mov word ptr [FontPtr+2],es + mov ah,11h ;BIOS character generator function + mov al,30h ;BIOS get font pointer subfunction + mov bh,3 ;get 8x8 ROM font subsubfunction + int 10h ;get the pointer to the BIOS 8x8 font + mov word ptr [FontPtr],bp + mov word ptr [FontPtr+2],es - mov bx,CHAR_HEIGHT - mov [CharHeight],bx ;# of scan lines per character - mov ax,SCREEN_HEIGHT - sub dx,dx - div bx - mul bx ;max # of full scan lines of text that - mov [MaxLines],ax ; will fit on the screen + mov bx,CHAR_HEIGHT + mov [CharHeight],bx ;# of scan lines per character + mov ax,SCREEN_HEIGHT + sub dx,dx + div bx + mul bx ;max # of full scan lines of text that + mov [MaxLines],ax ; will fit on the screen - mov ah,0fh ;BIOS video status function - int 10h ;get # of columns (bytes) per row - mov al,ah ;convert byte columns variable in - sub ah,ah ; AH to word in AX - mov [LineWidthBytes],ax ;width of scan line in bytes - ;now draw the text - sub bx,bx - mov [Line],bx ;start at scan line 0 - LineLoop: - sub ax,ax ;start at column 0; must be a multiple of 8 - mov ch,FG_COLOR ;color in which to draw text - mov cl,BG_COLOR ;color in which to draw background box - mov si,offset SampleString ;text to draw - call DrawTextString ;draw the sample text - mov bx,[Line] - add bx,[CharHeight] ;# of next scan line to draw on - mov [Line],bx - cmp bx,[MaxLines] ;done yet? - jb LineLoop ;not yet + mov ah,0fh ;BIOS video status function + int 10h ;get # of columns (bytes) per row + mov al,ah ;convert byte columns variable in + sub ah,ah ; AH to word in AX + mov [LineWidthBytes],ax ;width of scan line in bytes + ;now draw the text + sub bx,bx + mov [Line],bx ;start at scan line 0 +LineLoop: + sub ax,ax ;start at column 0; must be a multiple of 8 + mov ch,FG_COLOR ;color in which to draw text + mov cl,BG_COLOR ;color in which to draw background box + mov si,offset SampleString ;text to draw + call DrawTextString ;draw the sample text + mov bx,[Line] + add bx,[CharHeight] ;# of next scan line to draw on + mov [Line],bx + cmp bx,[MaxLines] ;done yet? + jb LineLoop ;not yet - mov ah,7 - int 21h ;wait for a key press, without echo + mov ah,7 + int 21h ;wait for a key press, without echo - mov ax,03h - int 10h ;back to text mode + mov ax,03h + int 10h ;back to text mode - mov ah,4ch - int 21h ;exit to DOS + mov ah,4ch + int 21h ;exit to DOS - ; Draws a text string. - ; Input: AX = X coordinate at which to draw upper-left corner of first char - ; BX = Y coordinate at which to draw upper-left corner of first char - ; CH = foreground (text) color - ; CL = background (box) color - ; DS:SI = pointer to string to draw, zero terminated - ; 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. - ; The X coordinate is truncated to a multiple of 8. Characters are - ; assumed to be 8 pixels wide. - align 2 - DrawTextString proc near - cld - shr ax,1 ;byte address of starting X within scan line - shr ax,1 - shr ax,1 - mov di,ax - mov ax,[LineWidthBytes] - 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 - ; first scan line - ;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 - ; you understand that Bit Mask register and - ; CPU data are ANDed in write mode 3) - mov ax,(003h SHL 8) + G_MODE - out dx,ax ;select write mode 3 - mov ah,cl ;background color - mov al,SET_RESET - out dx,ax ;set the drawing color to background color - mov byte ptr es:[0ffffh],0ffh - ;write 8 pixels of the background - ; color to unused off-screen memory - 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 - ; 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! - DrawTextLoop: - lodsb ;next character to draw - and al,al ;end of string? - jz DrawTextDone ;yes - 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 - mov dx,[LineWidthBytes] ;offset from one line to next - dec dx ;compensate for STOSB - mov cx,[CharHeight]; - mul cl ;offset of character in font table - lds si,[FontPtr] ;point to font table - add si,ax ;point to start of character to draw - ;the following loop should be unrolled for - ; maximum performance! - DrawCharLoop: ;draw all lines of the character - mov sb ;getthe next byte of the character and draw - ; character; data is ANDed with Bit Mask - ; register to become bit mask, and selects - ; between latch (containing the background - ; color) and Set/Reset register (containing - ; foreground color) - add di,dx ;point to next line of destination - loop DrawCharLoop +; Draws a text string. +; Input: AX = X coordinate at which to draw upper-left corner of first char +; BX = Y coordinate at which to draw upper-left corner of first char +; CH = foreground (text) color +; CL = background (box) color +; DS:SI = pointer to string to draw, zero terminated +; 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. +; The X coordinate is truncated to a multiple of 8. Characters are +; assumed to be 8 pixels wide. + align 2 + DrawTextString proc near + cld + shr ax,1 ;byte address of starting X within scan line + shr ax,1 + shr ax,1 + mov di,ax + mov ax,[LineWidthBytes] + 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 + ; first scan line + ;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 + ; you understand that Bit Mask register and + ; CPU data are ANDed in write mode 3) + mov ax,(003h SHL 8) + G_MODE + out dx,ax ;select write mode 3 + mov ah,cl ;background color + mov al,SET_RESET + out dx,ax ;set the drawing color to background color + mov byte ptr es:[0ffffh],0ffh + ;write 8 pixels of the background + ; color to unused off-screen memory + 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 + ; 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! +DrawTextLoop: + lodsb ;next character to draw + and al,al ;end of string? + jz DrawTextDone ;yes + 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 + mov dx,[LineWidthBytes] ;offset from one line to next + dec dx ;compensate for STOSB + mov cx,[CharHeight]; + mul cl ;offset of character in font table + lds si,[FontPtr] ;point to font table + add si,ax ;point to start of character to draw + ;the following loop should be unrolled for + ; maximum performance! +DrawCharLoop: ;draw all lines of the character + mov sb ;getthe next byte of the character and draw + ; character; data is ANDed with Bit Mask + ; register to become bit mask, and selects + ; between latch (containing the background + ; color) and Set/Reset register (containing + ; foreground color) + add di,dx ;point to next line of destination + loop DrawCharLoop - 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 - jmp DrawTextLoop ;draw next character, if any + 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 + jmp DrawTextLoop ;draw next character, if any - align2 - DrawTextDone: ;restore the Graphics Mode register to its - ; default state of write mode 0 - mov dx,GC_INDEX - mov ax,(000h SHL 8) + G_MODE - out dx,ax ;select write mode 0 - ret - DrawTextString endp - end start + align2 + DrawTextDone: ;restore the Graphics Mode register to its + ; default state of write mode 0 + mov dx,GC_INDEX + mov ax,(000h SHL 8) + G_MODE + out dx,ax ;select write mode 0 + ret +DrawTextString endp + end start +``` diff --git a/56-02.md b/56-02.md index f13a03f..0c6c99d 100644 --- a/56-02.md +++ b/56-02.md @@ -67,9 +67,7 @@ the source image.*](images/56-04.jpg) ![**Figure 56.5**  *Mapping a texture onto a narrower polygon.*](images/56-05.jpg) -#### Notes on DDA Texture Mapping\ - \ - {#Heading5} +#### Notes on DDA Texture Mapping {#Heading5} That's all there is to quick-and-dirty texture mapping. This technique basically uses a two-stage digital differential analyzer (DDA) approach diff --git a/56-03.md b/56-03.md index 375a156..fd60408 100644 --- a/56-03.md +++ b/56-03.md @@ -40,305 +40,309 @@ entirety. **LISTING 56.1 L56-1.C** - /* New header file entries related to texture-mapped polygons */ +```c +/* New header file entries related to texture-mapped polygons */ - /* Draws the polygon described by the point list PointList with a bitmap - texture mapped onto it */ - #define DRAW_TEXTURED_POLYGON(PointList,NumPoints,TexVerts,TexMap) \ - Polygon.Length = NumPoints; Polygon.PointPtr = PointList; \ - DrawTexturedPolygon(&Polygon, TexVerts, TexMap); - #define FIXED_TO_INT(FixedVal) ((int) (FixedVal >> 16)) - #define ROUND_FIXED_TO_INT(FixedVal) \ - ((int) ((FixedVal + DOUBLE_TO_FIXED(0.5)) >> 16)) - /* Retrieves specified pixel from specified image bitmap of specified width. */ - #define GET_IMAGE_PIXEL(TexMapBits, TexMapWidth, X, Y) \ - TexMapBits[(Y * TexMapWidth) + X] - /* Masks to mark shading types in Face structure */ - #define NO_SHADING 0x0000 - #define AMBIENT_SHADING 0x0001 - #define DIFFUSE_SHADING 0x0002 - #define TEXTURE_MAPPED_SHADING 0x0004 - /* Describes a texture map */ - typedef struct { - int TexMapWidth; /* texture map width in bytes */ - char *TexMapBits; /* pointer to texture bitmap */ - } TextureMap; +/* Draws the polygon described by the point list PointList with a bitmap + texture mapped onto it */ +#define DRAW_TEXTURED_POLYGON(PointList,NumPoints,TexVerts,TexMap) \ + Polygon.Length = NumPoints; Polygon.PointPtr = PointList; \ + DrawTexturedPolygon(&Polygon, TexVerts, TexMap); +#define FIXED_TO_INT(FixedVal) ((int) (FixedVal >> 16)) +#define ROUND_FIXED_TO_INT(FixedVal) \ + ((int) ((FixedVal + DOUBLE_TO_FIXED(0.5)) >> 16)) +/* Retrieves specified pixel from specified image bitmap of specified width. */ +#define GET_IMAGE_PIXEL(TexMapBits, TexMapWidth, X, Y) \ + TexMapBits[(Y * TexMapWidth) + X] +/* Masks to mark shading types in Face structure */ +#define NO_SHADING 0x0000 +#define AMBIENT_SHADING 0x0001 +#define DIFFUSE_SHADING 0x0002 +#define TEXTURE_MAPPED_SHADING 0x0004 +/* Describes a texture map */ +typedef struct { + int TexMapWidth; /* texture map width in bytes */ + char *TexMapBits; /* pointer to texture bitmap */ +} TextureMap; - /* 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 - must select end and start points, respectively, of this - 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 */ - 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 - TextureMap coordinates. Index n must map to index - n + 1 in VertNums, (the + 1 is to skip over the unit - normal endpoint in VertNums) */ - } Face; - extern void DrawTexturedPolygon(PointListHeader *, Point *, TextureMap *); +/* 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 + must select end and start points, respectively, of this + 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 */ + 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 + TextureMap coordinates. Index n must map to index + n + 1 in VertNums, (the + 1 is to skip over the unit + normal endpoint in VertNums) */ +} Face; +extern void DrawTexturedPolygon(PointListHeader *, Point *, TextureMap *); +``` **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 - 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. */ - #include - #include - #include "polygon.h" - /* Describes the current location and stepping, in both the source and - the destination, of an edge */ - typedef struct { - int Direction; /* through edge list; 1 for a right edge (forward - through vertex list), -1 for a left edge (backward - through vertex list) */ - int RemainingScans; /* height left to scan out in dest */ - int CurrentEnd; /* vertex # of end of current edge */ - Fixedpoint SourceX; /* current X location in source for this edge */ - 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 - X stepping through the dest, needed for precise - pixel placement to avoid gaps */ - int DestX; /* current X location in dest for this edge */ - int DestXIntStep; /* whole part of dest X step per scan-line Y step */ - int DestXDirection; /* -1 or 1 to indicate way X steps (left/right) */ - int DestXErrTerm; /* current error term for dest X stepping */ - int DestXAdjUp; /* amount to add to error term per scan line move */ - int DestXAdjDown; /* amount to subtract from error term when the - error term turns over */ - } EdgeScan; - int StepEdge(EdgeScan *); - int SetUpEdge(EdgeScan *, int); - void ScanOutLine(EdgeScan *, EdgeScan *); - int GetImagePixel(char *, int, int, int); - /* Statics to save time that would otherwise pass them to subroutines. */ - static int MaxVert, NumVerts, DestY; - static Point * VertexPtr; - static Point * TexVertsPtr; - static char * TexMapBits; - 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. */ - void DrawTexturedPolygon(PointListHeader * Polygon, Point * TexVerts, - TextureMap * TexMap) - { - int MinY, MaxY, MinVert, i; - EdgeScan LeftEdge, RightEdge; - NumVerts = Polygon->Length; - VertexPtr = Polygon->PointPtr; - TexVertsPtr = TexVerts; - TexMapBits = TexMap->TexMapBits; - TexMapWidth = TexMap->TexMapWidth; - /* Nothing to draw if less than 3 vertices */ - if (NumVerts < 3) { - return; - } - /* 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 - backface removal) */ - MinY = 32767; - MaxY = -32768; - for (i=0; i MaxY) { - MaxY = VertexPtr[i].Y; - MaxVert = i; - } - } - /* Reject flat (0-pixel-high) polygons */ - if (MinY >= MaxY) { - return; - } - /* The destination Y coordinate is not edge specific; it applies to - both edges, since we always step Y by 1 */ - DestY = MinY; - /* Set up to scan the initial left and right edges of the source and - destination polygons. We always step the destination polygon edges - by one in Y, so calculate the corresponding destination X step for - each edge, and then the corresponding source image X and Y steps */ - LeftEdge.Direction = -1; /* set up left edge first */ - SetUpEdge(&LeftEdge, MinVert); - RightEdge.Direction = 1; /* set up right edge */ - SetUpEdge(&RightEdge, MinVert); - /* Step down destination edges one scan line at a time. At each scan - line, find the corresponding edge points in the source image. Scan - between the edge points in the source, drawing the corresponding - pixels across the current scan line in the destination polygon. (We - know which way the left and right edges run through the vertex list - because visible (non-backface-culled) polygons always have the vertices - in clockwise order as seen from the viewpoint) */ - for (;;) { - /* Done if off bottom of clip rectangle */ - if (DestY >= ClipMaxY) { - return; - } - /* Draw only if inside Y bounds of clip rectangle */ - if (DestY >= ClipMinY) { - /* Draw the scan line between the two current edges */ - ScanOutLine(&LeftEdge, &RightEdge); - } - /* 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; - } - if (!StepEdge(&RightEdge)) { - break; - } - DestY++; - } - } - /* Steps an edge one scan line in the destination, and the corresponding - distance in the source. If an edge runs out, starts a new edge if there - is one. Returns 1 for success, or 0 if there are no more edges to scan. */ - int StepEdge(EdgeScan * Edge) - { - /* Count off the scan line we stepped last time; if this edge is - finished, try to start another one */ - if (--Edge->RemainingScans == 0) { - /* Set up the next edge; done if there is no next edge */ - if (SetUpEdge(Edge, Edge->CurrentEnd) == 0) { - return(0); /* no more edges; done drawing polygon */ - } - return(1); /* all set to draw the new edge */ - } - /* Step the current source edge */ - Edge->SourceX += Edge->SourceStepX; - Edge->SourceY += Edge->SourceStepY; - /* Step dest X with Bresenham-style variables, to get precise dest pixel - placement and avoid gaps */ - Edge->DestX += Edge->DestXIntStep; /* whole pixel step */ - /* Do error term stuff for fractional pixel X step handling */ - if ((Edge->DestXErrTerm += Edge->DestXAdjUp) > 0) { - Edge->DestX += Edge->DestXDirection; - Edge->DestXErrTerm -= Edge->DestXAdjDown; - } - return(1); - } - /* Sets up an edge to be scanned; the edge starts at StartVert and proceeds - in direction Edge->Direction through the vertex list. Edge->Direction must - be set prior to call; -1 to scan a left edge (backward through the vertex - list), 1 to scan a right edge (forward through the vertex list). - Automatically skips over 0-height edges. Returns 1 for success, or 0 if - there are no more edges to scan. */ - int SetUpEdge(EdgeScan * Edge, int StartVert) - { - int NextVert, DestXWidth; - Fixedpoint DestYHeight; - for (;;) { - /* Done if this edge starts at the bottom vertex */ - if (StartVert == MaxVert) { - return(0); - } - /* Advance to the next vertex, wrapping if we run off the start or end - of the vertex list */ - NextVert = StartVert + Edge->Direction; - if (NextVert >= NumVerts) { - NextVert = 0; - } else if (NextVert < 0) { - NextVert = NumVerts - 1; - } - /* Calculate the variables for this edge and done if this is not a - zero-height edge */ - if ((Edge->RemainingScans = - VertexPtr[NextVert].Y - VertexPtr[StartVert].Y) != 0) { - DestYHeight = INT_TO_FIXED(Edge->RemainingScans); - Edge->CurrentEnd = NextVert; - Edge->SourceX = INT_TO_FIXED(TexVertsPtr[StartVert].X); - Edge->SourceY = INT_TO_FIXED(TexVertsPtr[StartVert].Y); - Edge->SourceStepX = FixedDiv(INT_TO_FIXED(TexVertsPtr[NextVert].X) - - Edge->SourceX, DestYHeight); - Edge->SourceStepY = FixedDiv(INT_TO_FIXED(TexVertsPtr[NextVert].Y) - - Edge->SourceY, DestYHeight); - /* Set up Bresenham-style variables for dest X stepping */ - Edge->DestX = VertexPtr[StartVert].X; - if ((DestXWidth = - (VertexPtr[NextVert].X - VertexPtr[StartVert].X)) < 0) { - /* Set up for drawing right to left */ - Edge->DestXDirection = -1; - DestXWidth = -DestXWidth; - Edge->DestXErrTerm = 1 - Edge->RemainingScans; - Edge->DestXIntStep = -(DestXWidth / Edge->RemainingScans); - } else { - /* Set up for drawing left to right */ - Edge->DestXDirection = 1; - Edge->DestXErrTerm = 0; - Edge->DestXIntStep = DestXWidth / Edge->RemainingScans; - } - Edge->DestXAdjUp = DestXWidth % Edge->RemainingScans; - Edge->DestXAdjDown = Edge->RemainingScans; - return(1); /* success */ - } - StartVert = NextVert; /* keep looking for a non-0-height edge */ - } - } - /* Texture-map-draw the scan line between two edges. */ - void ScanOutLine(EdgeScan * LeftEdge, EdgeScan * RightEdge) - { - Fixedpoint SourceX = LeftEdge->SourceX; - Fixedpoint SourceY = LeftEdge->SourceY; - int DestX = LeftEdge->DestX; - int DestXMax = RightEdge->DestX; - Fixedpoint DestWidth; - Fixedpoint SourceXStep, SourceYStep; - /* Nothing to do if fully X clipped */ - if ((DestXMax <= ClipMinX) || (DestX >= ClipMaxX)) { - return; - } - if ((DestXMax - DestX) <= 0) { - return; /* nothing to draw */ - } - /* Width of destination scan line, for scaling. Note: because this is an - integer-based scaling, it can have a total error of as much as nearly - one pixel. For more precise scaling, also maintain a fixed-point DestX - in each edge, and use it for scaling. If this is done, it will also - be necessary to nudge the source start coordinates to the right by an - amount corresponding to the distance from the the real (fixed-point) - DestX and the first pixel (at an integer X) to be drawn) */ - DestWidth = INT_TO_FIXED(DestXMax - DestX); - /* Calculate source steps that correspond to each dest X step (across - the scan line) */ - SourceXStep = FixedDiv(RightEdge->SourceX - SourceX, DestWidth); - SourceYStep = FixedDiv(RightEdge->SourceY - SourceY, DestWidth); - /* Clip right edge if necessary */ - if (DestXMax > ClipMaxX) { - DestXMax = ClipMaxX; - } - /* Clip left edge if necssary */ - if (DestX < ClipMinX) { - SourceX += SourceXStep * (ClipMinX - DestX); - SourceY += SourceYStep * (ClipMinX - DestX); - DestX = ClipMinX; - } - /* Scan across the destination scan line, updating the source image - position accordingly */ - for (; DestX +#include +#include "polygon.h" +/* Describes the current location and stepping, in both the source and + the destination, of an edge */ +typedef struct { + int Direction; /* through edge list; 1 for a right edge (forward + through vertex list), -1 for a left edge (backward + through vertex list) */ + int RemainingScans; /* height left to scan out in dest */ + int CurrentEnd; /* vertex # of end of current edge */ + Fixedpoint SourceX; /* current X location in source for this edge */ + 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 + X stepping through the dest, needed for precise + pixel placement to avoid gaps */ + int DestX; /* current X location in dest for this edge */ + int DestXIntStep; /* whole part of dest X step per scan-line Y step */ + int DestXDirection; /* -1 or 1 to indicate way X steps (left/right) */ + int DestXErrTerm; /* current error term for dest X stepping */ + int DestXAdjUp; /* amount to add to error term per scan line move */ + int DestXAdjDown; /* amount to subtract from error term when the + error term turns over */ +} EdgeScan; +int StepEdge(EdgeScan *); +int SetUpEdge(EdgeScan *, int); +void ScanOutLine(EdgeScan *, EdgeScan *); +int GetImagePixel(char *, int, int, int); +/* Statics to save time that would otherwise pass them to subroutines. */ +static int MaxVert, NumVerts, DestY; +static Point * VertexPtr; +static Point * TexVertsPtr; +static char * TexMapBits; +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. */ +void DrawTexturedPolygon(PointListHeader * Polygon, Point * TexVerts, + TextureMap * TexMap) +{ + int MinY, MaxY, MinVert, i; + EdgeScan LeftEdge, RightEdge; + NumVerts = Polygon->Length; + VertexPtr = Polygon->PointPtr; + TexVertsPtr = TexVerts; + TexMapBits = TexMap->TexMapBits; + TexMapWidth = TexMap->TexMapWidth; + /* Nothing to draw if less than 3 vertices */ + if (NumVerts < 3) { + return; + } + /* 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 + backface removal) */ + MinY = 32767; + MaxY = -32768; + for (i=0; i MaxY) { + MaxY = VertexPtr[i].Y; + MaxVert = i; + } + } + /* Reject flat (0-pixel-high) polygons */ + if (MinY >= MaxY) { + return; + } + /* The destination Y coordinate is not edge specific; it applies to + both edges, since we always step Y by 1 */ + DestY = MinY; + /* Set up to scan the initial left and right edges of the source and + destination polygons. We always step the destination polygon edges + by one in Y, so calculate the corresponding destination X step for + each edge, and then the corresponding source image X and Y steps */ + LeftEdge.Direction = -1; /* set up left edge first */ + SetUpEdge(&LeftEdge, MinVert); + RightEdge.Direction = 1; /* set up right edge */ + SetUpEdge(&RightEdge, MinVert); + /* Step down destination edges one scan line at a time. At each scan + line, find the corresponding edge points in the source image. Scan + between the edge points in the source, drawing the corresponding + pixels across the current scan line in the destination polygon. (We + know which way the left and right edges run through the vertex list + because visible (non-backface-culled) polygons always have the vertices + in clockwise order as seen from the viewpoint) */ + for (;;) { + /* Done if off bottom of clip rectangle */ + if (DestY >= ClipMaxY) { + return; + } + /* Draw only if inside Y bounds of clip rectangle */ + if (DestY >= ClipMinY) { + /* Draw the scan line between the two current edges */ + ScanOutLine(&LeftEdge, &RightEdge); + } + /* 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; + } + if (!StepEdge(&RightEdge)) { + break; + } + DestY++; + } +} +/* Steps an edge one scan line in the destination, and the corresponding + distance in the source. If an edge runs out, starts a new edge if there + is one. Returns 1 for success, or 0 if there are no more edges to scan. */ +int StepEdge(EdgeScan * Edge) +{ + /* Count off the scan line we stepped last time; if this edge is + finished, try to start another one */ + if (--Edge->RemainingScans == 0) { + /* Set up the next edge; done if there is no next edge */ + if (SetUpEdge(Edge, Edge->CurrentEnd) == 0) { + return(0); /* no more edges; done drawing polygon */ + } + return(1); /* all set to draw the new edge */ + } + /* Step the current source edge */ + Edge->SourceX += Edge->SourceStepX; + Edge->SourceY += Edge->SourceStepY; + /* Step dest X with Bresenham-style variables, to get precise dest pixel + placement and avoid gaps */ + Edge->DestX += Edge->DestXIntStep; /* whole pixel step */ + /* Do error term stuff for fractional pixel X step handling */ + if ((Edge->DestXErrTerm += Edge->DestXAdjUp) > 0) { + Edge->DestX += Edge->DestXDirection; + Edge->DestXErrTerm -= Edge->DestXAdjDown; + } + return(1); +} +/* Sets up an edge to be scanned; the edge starts at StartVert and proceeds + in direction Edge->Direction through the vertex list. Edge->Direction must + be set prior to call; -1 to scan a left edge (backward through the vertex + list), 1 to scan a right edge (forward through the vertex list). + Automatically skips over 0-height edges. Returns 1 for success, or 0 if + there are no more edges to scan. */ +int SetUpEdge(EdgeScan * Edge, int StartVert) +{ + int NextVert, DestXWidth; + Fixedpoint DestYHeight; + for (;;) { + /* Done if this edge starts at the bottom vertex */ + if (StartVert == MaxVert) { + return(0); + } + /* Advance to the next vertex, wrapping if we run off the start or end + of the vertex list */ + NextVert = StartVert + Edge->Direction; + if (NextVert >= NumVerts) { + NextVert = 0; + } else if (NextVert < 0) { + NextVert = NumVerts - 1; + } + /* Calculate the variables for this edge and done if this is not a + zero-height edge */ + if ((Edge->RemainingScans = + VertexPtr[NextVert].Y - VertexPtr[StartVert].Y) != 0) { + DestYHeight = INT_TO_FIXED(Edge->RemainingScans); + Edge->CurrentEnd = NextVert; + Edge->SourceX = INT_TO_FIXED(TexVertsPtr[StartVert].X); + Edge->SourceY = INT_TO_FIXED(TexVertsPtr[StartVert].Y); + Edge->SourceStepX = FixedDiv(INT_TO_FIXED(TexVertsPtr[NextVert].X) - + Edge->SourceX, DestYHeight); + Edge->SourceStepY = FixedDiv(INT_TO_FIXED(TexVertsPtr[NextVert].Y) - + Edge->SourceY, DestYHeight); + /* Set up Bresenham-style variables for dest X stepping */ + Edge->DestX = VertexPtr[StartVert].X; + if ((DestXWidth = + (VertexPtr[NextVert].X - VertexPtr[StartVert].X)) < 0) { + /* Set up for drawing right to left */ + Edge->DestXDirection = -1; + DestXWidth = -DestXWidth; + Edge->DestXErrTerm = 1 - Edge->RemainingScans; + Edge->DestXIntStep = -(DestXWidth / Edge->RemainingScans); + } else { + /* Set up for drawing left to right */ + Edge->DestXDirection = 1; + Edge->DestXErrTerm = 0; + Edge->DestXIntStep = DestXWidth / Edge->RemainingScans; + } + Edge->DestXAdjUp = DestXWidth % Edge->RemainingScans; + Edge->DestXAdjDown = Edge->RemainingScans; + return(1); /* success */ + } + StartVert = NextVert; /* keep looking for a non-0-height edge */ + } +} +/* Texture-map-draw the scan line between two edges. */ +void ScanOutLine(EdgeScan * LeftEdge, EdgeScan * RightEdge) +{ + Fixedpoint SourceX = LeftEdge->SourceX; + Fixedpoint SourceY = LeftEdge->SourceY; + int DestX = LeftEdge->DestX; + int DestXMax = RightEdge->DestX; + Fixedpoint DestWidth; + Fixedpoint SourceXStep, SourceYStep; + /* Nothing to do if fully X clipped */ + if ((DestXMax <= ClipMinX) || (DestX >= ClipMaxX)) { + return; + } + if ((DestXMax - DestX) <= 0) { + return; /* nothing to draw */ + } + /* Width of destination scan line, for scaling. Note: because this is an + integer-based scaling, it can have a total error of as much as nearly + one pixel. For more precise scaling, also maintain a fixed-point DestX + in each edge, and use it for scaling. If this is done, it will also + be necessary to nudge the source start coordinates to the right by an + amount corresponding to the distance from the the real (fixed-point) + DestX and the first pixel (at an integer X) to be drawn) */ + DestWidth = INT_TO_FIXED(DestXMax - DestX); + /* Calculate source steps that correspond to each dest X step (across + the scan line) */ + SourceXStep = FixedDiv(RightEdge->SourceX - SourceX, DestWidth); + SourceYStep = FixedDiv(RightEdge->SourceY - SourceY, DestWidth); + /* Clip right edge if necessary */ + if (DestXMax > ClipMaxX) { + DestXMax = ClipMaxX; + } + /* Clip left edge if necssary */ + if (DestX < ClipMinX) { + SourceX += SourceXStep * (ClipMinX - DestX); + SourceY += SourceYStep * (ClipMinX - DestX); + DestX = ClipMinX; + } + /* Scan across the destination scan line, updating the source image + position accordingly */ + for (; DestXDestX; - int DestXMax = RightEdge->DestX; - Fixedpoint DestWidth; - Fixedpoint SourceStepX, SourceStepY; +void ScanOutLine(EdgeScan * LeftEdge, EdgeScan * RightEdge) +{ + Fixedpoint SourceX; + Fixedpoint SourceY; + int DestX = LeftEdge->DestX; + int DestXMax = RightEdge->DestX; + Fixedpoint DestWidth; + Fixedpoint SourceStepX, SourceStepY; - /* Nothing to do if fully X clipped */ - if ((DestXMax <= ClipMinX) || (DestX >= ClipMaxX)) { - return; - } + /* Nothing to do if fully X clipped */ + if ((DestXMax <= ClipMinX) || (DestX >= ClipMaxX)) { + return; + } - if ((DestXMax - DestX) <= 0) { - return; /* nothing to draw */ - } - SourceX = LeftEdge->SourceX; - SourceY = LeftEdge->SourceY; + if ((DestXMax - DestX) <= 0) { + return; /* nothing to draw */ + } + SourceX = LeftEdge->SourceX; + SourceY = LeftEdge->SourceY; - /* Width of destination scan line, for scaling. Note: because this is an - integer-based scaling, it can have a total error of as much as nearly - one pixel. For more precise scaling, also maintain a fixed-point DestX - in each edge, and use it for scaling. If this is done, it will also - be necessary to nudge the source start coordinates to the right by an - amount corresponding to the distance from the the real (fixed-point) - DestX and the first pixel (at an integer X) to be drawn). */ - DestWidth = INT-TO-FIXED(DestXMax - DestX); + /* Width of destination scan line, for scaling. Note: because this is an + integer-based scaling, it can have a total error of as much as nearly + one pixel. For more precise scaling, also maintain a fixed-point DestX + in each edge, and use it for scaling. If this is done, it will also + be necessary to nudge the source start coordinates to the right by an + amount corresponding to the distance from the the real (fixed-point) + DestX and the first pixel (at an integer X) to be drawn). */ + DestWidth = INT-TO-FIXED(DestXMax - DestX); - /* Calculate source steps that correspond to each dest X step (across - the scan line) */ - SourceStepX = FixedDiv(RightEdge->SourceX - SourceX, DestWidth); - SourceStepY = FixedDiv(RightEdge->SourceY - SourceY, DestWidth); + /* Calculate source steps that correspond to each dest X step (across + the scan line) */ + SourceStepX = FixedDiv(RightEdge->SourceX - SourceX, DestWidth); + 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 - 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.) */ - SourceX += SourceStepX >> 1; - SourceY += SourceStepY >> 1; + /* 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 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.) */ + SourceX += SourceStepX >> 1; + SourceY += SourceStepY >> 1; - /* Clip right edge if necssary */ - if (DestXMax > ClipMaxX) - DestXMax = ClipMaxX; + /* Clip right edge if necssary */ + if (DestXMax > ClipMaxX) + DestXMax = ClipMaxX; - /* Clip left edge if necssary */ - if (DestX < ClipMinX) { - SourceX += FixedMul(SourceStepX, INT-TO-FIXED(ClipMinX - DestX)); - SourceY += FixedMul(SourceStepY, INT-TO-FIXED(ClipMinX - DestX)); - DestX = ClipMinX; - } - /* Scan across the destination scan line, updating the source image - position accordingly */ - for (; DestXBackChild) { - WalkBSPTree(pNode->BackChild); - } - Draw(pNode); - if (pNode->FrontChild) { - WalkBSPTree(pNode->FrontChild); - } - } else { - if (pNode->FrontChild) { - WalkBSPTree(pNode->FrontChild); - } - Draw(pNode); - if (pNode->BackChild) { - WalkBSPTree(pNode->BackChild); - } - } - } +```c +void WalkBSPTree(NODE *pNode) +{ + if (WallFacingForward(pNode) { + if (pNode->BackChild) { + WalkBSPTree(pNode->BackChild); + } + Draw(pNode); + if (pNode->FrontChild) { + WalkBSPTree(pNode->FrontChild); + } + } else { + if (pNode->FrontChild) { + WalkBSPTree(pNode->FrontChild); + } + Draw(pNode); + if (pNode->BackChild) { + WalkBSPTree(pNode->BackChild); + } + } +} +``` > ![](images/i.jpg) > Be aware that BSP trees can often be made smaller and more efficient by diff --git a/59-04.md b/59-04.md index 6398e26..e650b99 100644 --- a/59-04.md +++ b/59-04.md @@ -43,38 +43,42 @@ unhesitatingly writes something like the perfectly good code in Listings **Listing 59.2 L59\_2.C** - // Function to inorder walk a tree, using code recursion. - // Tested with 32-bit Visual C++ 1.10. - #include - #include "tree.h" - extern void Visit(NODE *pNode); - void WalkTree(NODE *pNode) - { - // Make sure the tree isn't empty - if (pNode != NULL) - { - // Traverse the left subtree, if there is one - if (pNode->pLeftChild != NULL) - { - WalkTree(pNode->pLeftChild); - } - // Visit this node - Visit(pNode); - // Traverse the right subtree, if there is one - if (pNode->pRightChild != NULL) - { - WalkTree(pNode->pRightChild); - } - } - } +```c +// Function to inorder walk a tree, using code recursion. +// Tested with 32-bit Visual C++ 1.10. +#include +#include "tree.h" +extern void Visit(NODE *pNode); +void WalkTree(NODE *pNode) +{ + // Make sure the tree isn't empty + if (pNode != NULL) + { + // Traverse the left subtree, if there is one + if (pNode->pLeftChild != NULL) + { + WalkTree(pNode->pLeftChild); + } + // Visit this node + Visit(pNode); + // Traverse the right subtree, if there is one + if (pNode->pRightChild != NULL) + { + WalkTree(pNode->pRightChild); + } + } +} +``` **Listing 59.3 L59\_3.H** - // Header file TREE.H for tree-walking code. - typedef struct _NODE { - struct _NODE *pLeftChild; - struct _NODE *pRightChild; - } NODE; +```c +// Header file TREE.H for tree-walking code. +typedef struct _NODE { + struct _NODE *pLeftChild; + struct _NODE *pRightChild; +} 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. @@ -113,72 +117,74 @@ Listing 59.4, working with the code-recursive version as a model. **Listing 59.4 L59\_4.C** - // Function to inorder walk a tree, using data recursion. - // No stack overflow testing is performed. - // Tested with 32-bit Visual C++ 1.10. - #include - #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 - if (pNode != NULL) - { - NodeStack[0] = NULL; // push "stack empty" value - pNodeStack = NodeStack + 1; - for (;;) - { - // If the current node has a left child, push - // 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 - // visit in inorder sequence - while (pNode->pLeftChild != NULL) - { - *pNodeStack++ = pNode; - pNode = pNode->pLeftChild; - } - // 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 - // popped node until one with a right - // 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 - // with no left children) - for (;;) - { - Visit(pNode); - // If the node has a right child, make - // the child the current node and start - // traversing that subtree; otherwise, pop - // back up the tree, visiting nodes we - // passed on the way down, until we find a - // node with a right subtree to traverse - // or run out of pushed nodes and are done - if (pNode->pRightChild != NULL) - { - // Current node has a right child; - // traverse the right subtree - pNode = pNode->pRightChild; - break; - } - // Pop the next node from the stack so - // we can visit it and see if it has a - // right subtree to be traversed - if ((pNode = *—pNodeStack) == NULL) - { - // Stack is empty and the current node - // has no right child; we're done - return; - } - } - } - } - } +```c +// Function to inorder walk a tree, using data recursion. +// No stack overflow testing is performed. +// Tested with 32-bit Visual C++ 1.10. +#include +#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 + if (pNode != NULL) + { + NodeStack[0] = NULL; // push "stack empty" value + pNodeStack = NodeStack + 1; + for (;;) + { + // If the current node has a left child, push + // 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 + // visit in inorder sequence + while (pNode->pLeftChild != NULL) + { + *pNodeStack++ = pNode; + pNode = pNode->pLeftChild; + } + // 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 + // popped node until one with a right + // 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 + // with no left children) + for (;;) + { + Visit(pNode); + // If the node has a right child, make + // the child the current node and start + // traversing that subtree; otherwise, pop + // back up the tree, visiting nodes we + // passed on the way down, until we find a + // node with a right subtree to traverse + // or run out of pushed nodes and are done + if (pNode->pRightChild != NULL) + { + // Current node has a right child; + // traverse the right subtree + pNode = pNode->pRightChild; + break; + } + // Pop the next node from the stack so + // we can visit it and see if it has a + // right subtree to be traversed + if ((pNode = *—pNodeStack) == NULL) + { + // Stack is empty and the current node + // has no right child; we're done + return; + } + } + } + } +} +``` diff --git a/59-05.md b/59-05.md index e55b9c0..45b40bf 100644 --- a/59-05.md +++ b/59-05.md @@ -73,69 +73,71 @@ measuring!) **Listing 59.5 L59\_5.C** - // Sample program to exercise and time the performance of - // implementations of WalkTree(). - // Tested with 32-bit Visual C++ 1.10 under Windows NT. - #include - #include - #include - #include - #include "tree.h" - long VisitCount = 0; - void main(void); - void BuildTree(NODE *pNode, int RemainingDepth); - extern void WalkTree(NODE *pRootNode); - void main() - { - NODE RootNode; - int i; - long StartTime; - // Build a sample tree - BuildTree(&RootNode, 14); - // Walk the tree 1000 times and see how long it takes - StartTime = time(NULL); - for (i=0; i<1000; i++) - { - WalkTree(&RootNode); - } - printf("Seconds elapsed: %ld\n", - time(NULL) - StartTime); - getch(); - } - // - // Function to add right and left subtrees of the - // specified depth off the passed-in node. - // - void BuildTree(NODE *pNode, int RemainingDepth) - { - if (RemainingDepth == 0) - { - pNode->pLeftChild = NULL; - pNode->pRightChild = NULL; - } - else - { - pNode->pLeftChild = malloc(sizeof(NODE)); - if (pNode->pLeftChild == NULL) - { - printf("Out of memory\n"); - exit(1); - } - pNode->pRightChild = malloc(sizeof(NODE)); - if (pNode->pRightChild == NULL) - { - printf("Out of memory\n"); - exit(1); - } - BuildTree(pNode->pLeftChild, RemainingDepth - 1); - BuildTree(pNode->pRightChild, RemainingDepth - 1); - } - } - // - // Node-visiting function so WalkTree() has something to - // call. - // - void Visit(NODE *pNode) - { - VisitCount++; - } +```cpp +// Sample program to exercise and time the performance of +// implementations of WalkTree(). +// Tested with 32-bit Visual C++ 1.10 under Windows NT. +#include +#include +#include +#include +#include "tree.h" +long VisitCount = 0; +void main(void); +void BuildTree(NODE *pNode, int RemainingDepth); +extern void WalkTree(NODE *pRootNode); +void main() +{ + NODE RootNode; + int i; + long StartTime; + // Build a sample tree + BuildTree(&RootNode, 14); + // Walk the tree 1000 times and see how long it takes + StartTime = time(NULL); + for (i=0; i<1000; i++) + { + WalkTree(&RootNode); + } + printf("Seconds elapsed: %ld\n", + time(NULL) - StartTime); + getch(); +} +// +// Function to add right and left subtrees of the +// specified depth off the passed-in node. +// +void BuildTree(NODE *pNode, int RemainingDepth) +{ + if (RemainingDepth == 0) + { + pNode->pLeftChild = NULL; + pNode->pRightChild = NULL; + } + else + { + pNode->pLeftChild = malloc(sizeof(NODE)); + if (pNode->pLeftChild == NULL) + { + printf("Out of memory\n"); + exit(1); + } + pNode->pRightChild = malloc(sizeof(NODE)); + if (pNode->pRightChild == NULL) + { + printf("Out of memory\n"); + exit(1); + } + BuildTree(pNode->pLeftChild, RemainingDepth - 1); + BuildTree(pNode->pRightChild, RemainingDepth - 1); + } +} +// +// Node-visiting function so WalkTree() has something to +// call. +// +void Visit(NODE *pNode) +{ + VisitCount++; +} +``` diff --git a/60-03.md b/60-03.md index 6ff2631..8c40c57 100644 --- a/60-03.md +++ b/60-03.md @@ -12,260 +12,262 @@ pages: 1123-1127 **Listing 60.1 L60\_1.CPP** - #define MAX_NUM_LINESEGS 1000 - #define MAX_INT 0x7FFFFFFF - #define MATCH_TOLERANCE 0.00001 - // A vertex - typedef struct _VERTEX - { - double x; - double y; - } VERTEX; - // A potentially split piece of a line segment, as processed from the - // base line in the original list - typedef struct _LINESEG - { - _LINESEG *pnextlineseg; - int startvertex; - int endvertex; - double walltop; - double wallbottom; - double tstart; - double tend; - int color; - _LINESEG *pfronttree; - _LINESEG *pbacktree; - } LINESEG, *PLINESEG; - static VERTEX *pvertexlist; - static int NumCompiledLinesegs = 0; - 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. - // Returns NULL for errors. - LINESEG * SelectBSPTree(LINESEG * plineseghead, - LINESEG * pCurrentTree, LINESEG ** pParentsChildPointer) - { - LINESEG *pminsplit; - int minsplits; - int tempsplitcount; - LINESEG *prootline; - 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 - // the list that splits the fewest of the other lines in the list - minsplits = MAX_INT; - prootline = plineseghead; - while (prootline != NULL) { - pcurrentline = plineseghead; - tempsplitcount = 0; - while (pcurrentline != NULL) { - // See how many other lines the current line splits - nx = pvertexlist[prootline->startvertex].y - - pvertexlist[prootline->endvertex].y; - ny = -(pvertexlist[prootline->startvertex].x - - pvertexlist[prootline->endvertex].x); - // Calculate the dot products we'll need for line - // intersection and spatial relationship - numer = (nx * (pvertexlist[pcurrentline->startvertex].x - - pvertexlist[prootline->startvertex].x)) + - (ny * (pvertexlist[pcurrentline->startvertex].y - - pvertexlist[prootline->startvertex].y)); - denom = ((-nx) * (pvertexlist[pcurrentline->endvertex].x - - pvertexlist[pcurrentline->startvertex].x)) + - ((-ny) * (pvertexlist[pcurrentline->endvertex].y - - pvertexlist[pcurrentline->startvertex].y)); - // Figure out if the infinite lines of the current line - // and the root intersect; if so, figure out if the - // current line segment is actually split, split if so, - // and add front/back polygons as appropriate - if (denom == 0.0) { - // No intersection, because lines are parallel; no - // split, so nothing to do - } else { - // Infinite lines intersect; figure out whether the - // actual line segment intersects the infinite line - // of the root, and split if so - t = numer / denom; - if ((t > pcurrentline->tstart) && - (t < pcurrentline->tend)) { - // The root splits the current line - tempsplitcount++; - } else { - // Intersection outside segment limits, so no - // split, nothing to do - } - } - pcurrentline = pcurrentline->pnextlineseg; - } - if (tempsplitcount < minsplits) { - pminsplit = prootline; - minsplits = tempsplitcount; - } - prootline = prootline->pnextlineseg; - } - // For now, make this a leaf node so we can traverse the tree - // as it is at this point. BuildBSPTree() will add children as - // appropriate - pminsplit->pfronttree = NULL; - pminsplit->pbacktree = NULL; - // Point the parent's child pointer to this node, so we can - // track the currently-build tree - *pParentsChildPointer = pminsplit; - return BuildBSPTree(plineseghead, pminsplit, pCurrentTree); - } - // Builds a BSP tree given the specified root, by creating front and - // back lists from the remaining lines, and calling itself recursively - LINESEG * BuildBSPTree(LINESEG * plineseghead, LINESEG * prootline, - LINESEG * pCurrentTree) - { - LINESEG *pfrontlines; - LINESEG *pbacklines; - LINESEG *pcurrentline; - LINESEG *pnextlineseg; - 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 - pfrontlines = NULL; - pbacklines = NULL; +```cpp +#define MAX_NUM_LINESEGS 1000 +#define MAX_INT 0x7FFFFFFF +#define MATCH_TOLERANCE 0.00001 +// A vertex +typedef struct _VERTEX +{ + double x; + double y; +} VERTEX; +// A potentially split piece of a line segment, as processed from the +// base line in the original list +typedef struct _LINESEG +{ + _LINESEG *pnextlineseg; + int startvertex; + int endvertex; + double walltop; + double wallbottom; + double tstart; + double tend; + int color; + _LINESEG *pfronttree; + _LINESEG *pbacktree; +} LINESEG, *PLINESEG; +static VERTEX *pvertexlist; +static int NumCompiledLinesegs = 0; +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. +// Returns NULL for errors. +LINESEG * SelectBSPTree(LINESEG * plineseghead, + LINESEG * pCurrentTree, LINESEG ** pParentsChildPointer) +{ + LINESEG *pminsplit; + int minsplits; + int tempsplitcount; + LINESEG *prootline; + 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 + // the list that splits the fewest of the other lines in the list + minsplits = MAX_INT; + prootline = plineseghead; + while (prootline != NULL) { pcurrentline = plineseghead; - while (pcurrentline != NULL) - { - // Skip the root line when encountered - if (pcurrentline == prootline) { - pcurrentline = pcurrentline->pnextlineseg; - } else { + tempsplitcount = 0; + while (pcurrentline != NULL) { + // See how many other lines the current line splits nx = pvertexlist[prootline->startvertex].y - pvertexlist[prootline->endvertex].y; ny = -(pvertexlist[prootline->startvertex].x - pvertexlist[prootline->endvertex].x); - // Calculate the dot products we'll need for line intersection - // and spatial relationship + // Calculate the dot products we'll need for line + // intersection and spatial relationship numer = (nx * (pvertexlist[pcurrentline->startvertex].x - - pvertexlist[prootline->startvertex].x)) + + pvertexlist[prootline->startvertex].x)) + (ny * (pvertexlist[pcurrentline->startvertex].y - - pvertexlist[prootline->startvertex].y)); + pvertexlist[prootline->startvertex].y)); denom = ((-nx) * (pvertexlist[pcurrentline->endvertex].x - - pvertexlist[pcurrentline->startvertex].x)) + - (-(ny) * (pvertexlist[pcurrentline->endvertex].y - - pvertexlist[pcurrentline->startvertex].y)); - // Figure out if the infinite lines of the current line and - // the root intersect; if so, figure out if the current line - // segment is actually split, split if so, and add front/back - // polygons as appropriate + pvertexlist[pcurrentline->startvertex].x)) + + ((-ny) * (pvertexlist[pcurrentline->endvertex].y - + pvertexlist[pcurrentline->startvertex].y)); + // Figure out if the infinite lines of the current line + // and the root intersect; if so, figure out if the + // current line segment is actually split, split if so, + // and add front/back polygons as appropriate if (denom == 0.0) { - // No intersection, because lines are parallel; just add - // to appropriate list + // No intersection, because lines are parallel; no + // split, so nothing to do + } else { + // Infinite lines intersect; figure out whether the + // actual line segment intersects the infinite line + // of the root, and split if so + t = numer / denom; + if ((t > pcurrentline->tstart) && + (t < pcurrentline->tend)) { + // The root splits the current line + tempsplitcount++; + } else { + // Intersection outside segment limits, so no + // split, nothing to do + } + } + pcurrentline = pcurrentline->pnextlineseg; + } + if (tempsplitcount < minsplits) { + pminsplit = prootline; + minsplits = tempsplitcount; + } + prootline = prootline->pnextlineseg; + } + // For now, make this a leaf node so we can traverse the tree + // as it is at this point. BuildBSPTree() will add children as + // appropriate + pminsplit->pfronttree = NULL; + pminsplit->pbacktree = NULL; + // Point the parent's child pointer to this node, so we can + // track the currently-build tree + *pParentsChildPointer = pminsplit; + return BuildBSPTree(plineseghead, pminsplit, pCurrentTree); +} +// Builds a BSP tree given the specified root, by creating front and +// back lists from the remaining lines, and calling itself recursively +LINESEG * BuildBSPTree(LINESEG * plineseghead, LINESEG * prootline, + LINESEG * pCurrentTree) +{ + LINESEG *pfrontlines; + LINESEG *pbacklines; + LINESEG *pcurrentline; + LINESEG *pnextlineseg; + 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 + pfrontlines = NULL; + pbacklines = NULL; + pcurrentline = plineseghead; + while (pcurrentline != NULL) + { + // Skip the root line when encountered + if (pcurrentline == prootline) { + pcurrentline = pcurrentline->pnextlineseg; + } else { + nx = pvertexlist[prootline->startvertex].y - + pvertexlist[prootline->endvertex].y; + ny = -(pvertexlist[prootline->startvertex].x - + pvertexlist[prootline->endvertex].x); + // Calculate the dot products we'll need for line intersection + // and spatial relationship + numer = (nx * (pvertexlist[pcurrentline->startvertex].x - + pvertexlist[prootline->startvertex].x)) + + (ny * (pvertexlist[pcurrentline->startvertex].y - + pvertexlist[prootline->startvertex].y)); + denom = ((-nx) * (pvertexlist[pcurrentline->endvertex].x - + pvertexlist[pcurrentline->startvertex].x)) + + (-(ny) * (pvertexlist[pcurrentline->endvertex].y - + pvertexlist[pcurrentline->startvertex].y)); + // Figure out if the infinite lines of the current line and + // the root intersect; if so, figure out if the current line + // segment is actually split, split if so, and add front/back + // polygons as appropriate + if (denom == 0.0) { + // No intersection, because lines are parallel; just add + // to appropriate list + pnextlineseg = pcurrentline->pnextlineseg; + if (numer < 0.0) { + // Current line is in front of root line; link into + // front list + pcurrentline->pnextlineseg = pfrontlines; + pfrontlines = pcurrentline; + } else { + // Current line behind root line; link into back list + pcurrentline->pnextlineseg = pbacklines; + pbacklines = pcurrentline; + } + pcurrentline = pnextlineseg; + } else { + // Infinite lines intersect; figure out whether the actual + // line segment intersects the infinite line of the root, + // and split if so + t = numer / denom; + if ((t > pcurrentline->tstart) && + (t < pcurrentline->tend)) { + // 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"); + return NULL; + } + // Make a new line entry for the split part of line + psplitline = &pCompiledLinesegs[NumCompiledLinesegs]; + NumCompiledLinesegs++; + *psplitline = *pcurrentline; + psplitline->tstart = t; + pcurrentline->tend = t; + pnextlineseg = pcurrentline->pnextlineseg; if (numer < 0.0) { - // Current line is in front of root line; link into - // front list + // Presplit part is in front of root line; link + // into front list and put postsplit part in back + // list pcurrentline->pnextlineseg = pfrontlines; pfrontlines = pcurrentline; + psplitline->pnextlineseg = pbacklines; + pbacklines = psplitline; } else { - // Current line behind root line; link into back list + // Presplit part is in back of root line; link + // into back list and put postsplit part in front + // list + psplitline->pnextlineseg = pfrontlines; + pfrontlines = psplitline; pcurrentline->pnextlineseg = pbacklines; pbacklines = pcurrentline; } pcurrentline = pnextlineseg; } else { - // Infinite lines intersect; figure out whether the actual - // line segment intersects the infinite line of the root, - // and split if so - t = numer / denom; - if ((t > pcurrentline->tstart) && - (t < pcurrentline->tend)) { - // 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"); - return NULL; - } - // Make a new line entry for the split part of line - psplitline = &pCompiledLinesegs[NumCompiledLinesegs]; - NumCompiledLinesegs++; - *psplitline = *pcurrentline; - psplitline->tstart = t; - pcurrentline->tend = t; - - pnextlineseg = pcurrentline->pnextlineseg; - if (numer < 0.0) { - // Presplit part is in front of root line; link - // into front list and put postsplit part in back - // list + // Intersection outside segment limits, so no need to + // split; just add to proper list + pnextlineseg = pcurrentline->pnextlineseg; + Done = 0; + while (!Done) { + if (numer < -MATCH_TOLERANCE) { + // Current line is in front of root line; + // link into front list pcurrentline->pnextlineseg = pfrontlines; pfrontlines = pcurrentline; - psplitline->pnextlineseg = pbacklines; - pbacklines = psplitline; - } else { - // Presplit part is in back of root line; link - // into back list and put postsplit part in front - // list - psplitline->pnextlineseg = pfrontlines; - pfrontlines = psplitline; + Done = 1; + } else if (numer > MATCH_TOLERANCE) { + // Current line is behind root line; link + // into back list pcurrentline->pnextlineseg = pbacklines; pbacklines = pcurrentline; + Done = 1; + } else { + // The point on the current line we picked to + // do front/back evaluation happens to be + // collinear with the root, so use the other + // end of the current line and try again + numer = + (nx * + (pvertexlist[pcurrentline->endvertex].x - + pvertexlist[prootline->startvertex].x))+ + (ny * + (pvertexlist[pcurrentline->endvertex].y - + pvertexlist[prootline->startvertex].y)); } - pcurrentline = pnextlineseg; - } else { - // Intersection outside segment limits, so no need to - // split; just add to proper list - pnextlineseg = pcurrentline->pnextlineseg; - Done = 0; - while (!Done) { - if (numer < -MATCH_TOLERANCE) { - // Current line is in front of root line; - // link into front list - pcurrentline->pnextlineseg = pfrontlines; - pfrontlines = pcurrentline; - Done = 1; - } else if (numer > MATCH_TOLERANCE) { - // Current line is behind root line; link - // into back list - pcurrentline->pnextlineseg = pbacklines; - pbacklines = pcurrentline; - Done = 1; - } else { - // The point on the current line we picked to - // do front/back evaluation happens to be - // collinear with the root, so use the other - // end of the current line and try again - numer = - (nx * - (pvertexlist[pcurrentline->endvertex].x - - pvertexlist[prootline->startvertex].x))+ - (ny * - (pvertexlist[pcurrentline->endvertex].y - - pvertexlist[prootline->startvertex].y)); - } - } - pcurrentline = pnextlineseg; } - } - } - } - // Make a node out of the root line, with the front and back trees - // attached - if (pfrontlines == NULL) { - prootline->pfronttree = NULL; - } else { - if (!SelectBSPTree(pfrontlines, pCurrentTree, - &prootline->pfronttree)) { - return NULL; + pcurrentline = pnextlineseg; } } - if (pbacklines == NULL) { - prootline->pbacktree = NULL; - } else { - if (!SelectBSPTree(pbacklines, pCurrentTree, - &prootline->pbacktree)) { - return NULL; - } - } - return(prootline); + } } + // Make a node out of the root line, with the front and back trees + // attached + if (pfrontlines == NULL) { + prootline->pfronttree = NULL; + } else { + if (!SelectBSPTree(pfrontlines, pCurrentTree, + &prootline->pfronttree)) { + return NULL; + } + } + if (pbacklines == NULL) { + prootline->pbacktree = NULL; + } else { + if (!SelectBSPTree(pbacklines, pCurrentTree, + &prootline->pbacktree)) { + return NULL; + } + } + return(prootline); +} +``` diff --git a/61-04.md b/61-04.md index d8a490f..66c9e21 100644 --- a/61-04.md +++ b/61-04.md @@ -50,35 +50,37 @@ point, as shown in Listing 61.1. **LISTING 61.1 L61\_1.C** - // Given two line endpoints, a point on a plane, and a unit normal - // for the plane, returns the point of intersection of the line - // and the plane in intersectpoint. - #define DOT_PRODUCT(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) - void LineIntersectPlane (float *linestart, float *lineend, - float *planeorigin, float *planenormal, float *intersectpoint) - { - float vec1[3], projectedlinelength, startdistfromplane, scale; - vec1[0] = linestart[0] - planeorigin[0]; - vec1[1] = linestart[1] - planeorigin[1]; - vec1[2] = linestart[2] - planeorigin[2]; - startdistfromplane = DOT_PRODUCT(vec1, planenormal); - if (startdistfromplane == 0) - { - // point is in plane - intersectpoint[0] = linestart[0]; - intersectpoint[1] = linestart[1]; - intersectpoint[2] = linestart[1]; - return; - } - vec1[0] = linestart[0] - lineend[0]; - vec1[1] = linestart[1] - lineend[1]; - vec1[2] = linestart[2] - lineend[2]; - projectedlinelength = DOT_PRODUCT(vec1, planenormal); - scale = startdistfromplane / projectedlinelength; - intersectpoint[0] = linestart[0] - vec1[0] * scale; - intersectpoint[1] = linestart[1] - vec1[1] * scale; - intersectpoint[2] = linestart[1] - vec1[2] * scale; - } +```c +// Given two line endpoints, a point on a plane, and a unit normal +// for the plane, returns the point of intersection of the line +// and the plane in intersectpoint. +#define DOT_PRODUCT(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +void LineIntersectPlane (float *linestart, float *lineend, + float *planeorigin, float *planenormal, float *intersectpoint) +{ + float vec1[3], projectedlinelength, startdistfromplane, scale; + vec1[0] = linestart[0] - planeorigin[0]; + vec1[1] = linestart[1] - planeorigin[1]; + vec1[2] = linestart[2] - planeorigin[2]; + startdistfromplane = DOT_PRODUCT(vec1, planenormal); + if (startdistfromplane == 0) + { + // point is in plane + intersectpoint[0] = linestart[0]; + intersectpoint[1] = linestart[1]; + intersectpoint[2] = linestart[1]; + return; + } + vec1[0] = linestart[0] - lineend[0]; + vec1[1] = linestart[1] - lineend[1]; + vec1[2] = linestart[2] - lineend[2]; + projectedlinelength = DOT_PRODUCT(vec1, planenormal); + scale = startdistfromplane / projectedlinelength; + intersectpoint[0] = linestart[0] - vec1[0] * scale; + intersectpoint[1] = linestart[1] - vec1[1] * scale; + intersectpoint[2] = linestart[1] - vec1[2] * scale; +} +``` ### Rotation by Projection {#Heading10} diff --git a/62-02.md b/62-02.md index 13fc68f..a3f6baf 100644 --- a/62-02.md +++ b/62-02.md @@ -12,442 +12,444 @@ pages: 1149-1157 **Listing 62.1 L62\_1.C** - /* Core renderer for Win32 program to demonstrate drawing from a 2-D - BSP tree; illustrate the use of BSP trees for surface visibility. - UpdateWorld() is the top-level function in this module. - Full source code for the BSP-based renderer, and for the - accompanying BSP compiler, may be downloaded from - ftp.idsoftware.com/mikeab, in the file ddjbsp2.zip. - Tested with VC++ 2.0 running on Windows NT 3.5. */ - #define FIXEDPOINT(x) ((FIXEDPOINT)(((long)x)*((long)0x10000))) - #define FIXTOINT(x) ((int)(x >> 16)) - #define ANGLE(x) ((long)x) - #define STANDARD_SPEED (FIXEDPOINT(20)) - #define STANDARD_ROTATION (ANGLE(4)) - #define MAX_NUM_NODES 2000 - #define MAX_NUM_EXTRA_VERTICES 2000 - #define WORLD_MIN_X (FIXEDPOINT(-16000)) - #define WORLD_MAX_X (FIXEDPOINT(16000)) - #define WORLD_MIN_Y (FIXEDPOINT(-16000)) - #define WORLD_MAX_Y (FIXEDPOINT(16000)) - #define WORLD_MIN_Z (FIXEDPOINT(-16000)) - #define WORLD_MAX_Z (FIXEDPOINT(16000)) - #define PROJECTION_RATIO (2.0/1.0) // controls field of view; the - // bigger this is, the narrower the field of view - typedef long FIXEDPOINT; - typedef struct _VERTEX { - FIXEDPOINT x, z, viewx, viewz; - } VERTEX, *PVERTEX; - typedef struct _POINT2 { FIXEDPOINT x, z; } POINT2, *PPOINT2; - typedef struct _POINT2INT { int x; int y; } POINT2INT, *PPOINT2INT; - typedef long ANGLE; // angles are stored in degrees - typedef struct _NODE { - VERTEX *pstartvertex, *pendvertex; - FIXEDPOINT walltop, wallbottom, tstart, tend; - FIXEDPOINT clippedtstart, clippedtend; - struct _NODE *fronttree, *backtree; - int color, isVisible; - FIXEDPOINT screenxstart, screenxend; - FIXEDPOINT screenytopstart, screenybottomstart; - FIXEDPOINT screenytopend, screenybottomend; - } NODE, *PNODE; - char * pDIB; // pointer to DIB section we'll draw into - HBITMAP hDIBSection; // handle of DIB section - HPALETTE hpalDIB; - int iteration = 0, WorldIsRunning = 1; - HWND hwndOutput; - int DIBWidth, DIBHeight, DIBPitch, numvertices, numnodes; - FIXEDPOINT fxHalfDIBWidth, fxHalfDIBHeight; - VERTEX *pvertexlist, *pextravertexlist; - NODE *pnodelist; - POINT2 currentlocation, currentdirection, currentorientation; - ANGLE currentangle; - FIXEDPOINT currentspeed, fxViewerY, currentYSpeed; - FIXEDPOINT FrontClipPlane = FIXEDPOINT(10); - FIXEDPOINT FixedMul(FIXEDPOINT x, FIXEDPOINT y); - FIXEDPOINT FixedDiv(FIXEDPOINT x, FIXEDPOINT y); - FIXEDPOINT FixedSin(ANGLE angle), FixedCos(ANGLE angle); - extern int FillConvexPolygon(POINT2INT * VertexPtr, int Color); - // Returns nonzero if a wall is facing the viewer, 0 else. - int WallFacingViewer(NODE * pwall) - { - FIXEDPOINT viewxstart = pwall->pstartvertex->viewx; - FIXEDPOINT viewzstart = pwall->pstartvertex->viewz; - FIXEDPOINT viewxend = pwall->pendvertex->viewx; - FIXEDPOINT viewzend = pwall->pendvertex->viewz; - int Temp; - /* // equivalent C code - if (( ((pwall->pstartvertex->viewx >> 16) * - ((pwall->pendvertex->viewz - - pwall->pstartvertex->viewz) >> 16)) + - ((pwall->pstartvertex->viewz >> 16) * - ((pwall->pstartvertex->viewx - - pwall->pendvertex->viewx) >> 16)) ) - < 0) - return(1); - else - return(0); - */ - _asm { - mov eax,viewzend - sub eax,viewzstart - imul viewxstart - mov ecx,edx - mov ebx,eax - mov eax,viewxstart - sub eax,viewxend - imul viewzstart - add eax,ebx - adc edx,ecx - mov eax,0 - jns short WFVDone - inc eax - WFVDone: - mov Temp,eax - } - return(Temp); - } - // Update the viewpoint position as needed. - void UpdateViewPos() - { - if (currentspeed != 0) { - currentlocation.x += FixedMul(currentdirection.x, - currentspeed); - if (currentlocation.x <= WORLD_MIN_X) - currentlocation.x = WORLD_MIN_X; - if (currentlocation.x >= WORLD_MAX_X) - currentlocation.x = WORLD_MAX_X - 1; - currentlocation.z += FixedMul(currentdirection.z, - currentspeed); - if (currentlocation.z <= WORLD_MIN_Z) - currentlocation.z = WORLD_MIN_Z; - if (currentlocation.z >= WORLD_MAX_Z) - currentlocation.z = WORLD_MAX_Z - 1; - } - if (currentYSpeed != 0) { - fxViewerY += currentYSpeed; - if (fxViewerY <= WORLD_MIN_Y) - fxViewerY = WORLD_MIN_Y; - if (fxViewerY >= WORLD_MAX_Y) - fxViewerY = WORLD_MAX_Y - 1; - } - } - // Transform all vertices into viewspace. - void TransformVertices() - { - VERTEX *pvertex; - FIXEDPOINT tempx, tempz; - int vertex; - pvertex = pvertexlist; - for (vertex = 0; vertex < numvertices; vertex++) { - // Translate the vertex according to the viewpoint - tempx = pvertex->x - currentlocation.x; - tempz = pvertex->z - currentlocation.z; - // Rotate the vertex so viewpoint is looking down z axis - pvertex->viewx = FixedMul(FixedMul(tempx, - currentorientation.z) + - FixedMul(tempz, -currentorientation.x), - FIXEDPOINT(PROJECTION_RATIO)); - pvertex->viewz = FixedMul(tempx, currentorientation.x) + - FixedMul(tempz, currentorientation.z); - pvertex++; - } - } - // 3-D clip all walls. If any part of each wall is still visible, - // transform to perspective viewspace. - void ClipWalls() - { - NODE *pwall; - int wall; - FIXEDPOINT tempstartx, tempendx, tempstartz, tempendz; - FIXEDPOINT tempstartwalltop, tempstartwallbottom; - FIXEDPOINT tempendwalltop, tempendwallbottom; - VERTEX *pstartvertex, *pendvertex; - VERTEX *pextravertex = pextravertexlist; - pwall = pnodelist; - for (wall = 0; wall < numnodes; wall++) { - // Assume the wall won't be visible - pwall->isVisible = 0; - // Generate the wall endpoints, accounting for t values and - // clipping - // Calculate the viewspace coordinates for this wall - pstartvertex = pwall->pstartvertex; - pendvertex = pwall->pendvertex; - // Look for z clipping first - // Calculate start and end z coordinates for this wall - if (pwall->tstart == FIXEDPOINT(0)) - tempstartz = pstartvertex->viewz; - else - tempstartz = pstartvertex->viewz + - FixedMul((pendvertex->viewz-pstartvertex->viewz), - pwall->tstart); - if (pwall->tend == FIXEDPOINT(1)) - tempendz = pendvertex->viewz; - else - tempendz = pstartvertex->viewz + - FixedMul((pendvertex->viewz-pstartvertex->viewz), - pwall->tend); - // Clip to the front plane - if (tempendz < FrontClipPlane) { - if (tempstartz < FrontClipPlane) { - // Fully front-clipped - goto NextWall; - } else { - pwall->clippedtstart = pwall->tstart; - // Clip the end point to the front clip plane - pwall->clippedtend = - FixedDiv(pstartvertex->viewz - FrontClipPlane, - pstartvertex->viewz-pendvertex->viewz); - tempendz = pstartvertex->viewz + - FixedMul((pendvertex->viewz-pstartvertex->viewz), - pwall->clippedtend); - } - } else { - pwall->clippedtend = pwall->tend; - if (tempstartz < FrontClipPlane) { - // Clip the start point to the front clip plane - pwall->clippedtstart = - FixedDiv(FrontClipPlane - pstartvertex->viewz, - pendvertex->viewz-pstartvertex->viewz); - tempstartz = pstartvertex->viewz + - FixedMul((pendvertex->viewz-pstartvertex->viewz), - pwall->clippedtstart); - } else { - pwall->clippedtstart = pwall->tstart; - } - } - // Calculate x coordinates - if (pwall->clippedtstart == FIXEDPOINT(0)) - tempstartx = pstartvertex->viewx; - else - tempstartx = pstartvertex->viewx + - FixedMul((pendvertex->viewx-pstartvertex->viewx), - pwall->clippedtstart); - if (pwall->clippedtend == FIXEDPOINT(1)) - tempendx = pendvertex->viewx; - else - tempendx = pstartvertex->viewx + - FixedMul((pendvertex->viewx-pstartvertex->viewx), - pwall->clippedtend); - // Clip in x as needed - if ((tempstartx > tempstartz) || (tempstartx < -tempstartz)) { - // The start point is outside the view triangle in x; - // perform a quick test for trivial rejection by seeing if - // the end point is outside the view triangle on the same - // side as the start point - if (((tempstartx>tempstartz) && (tempendx>tempendz)) || - ((tempstartx<-tempstartz) && (tempendx<-tempendz))) - // Fully clipped—trivially reject - goto NextWall; - // Clip the start point - if (tempstartx > tempstartz) { - // Clip the start point on the right side - pwall->clippedtstart = - FixedDiv(pstartvertex->viewx-pstartvertex->viewz, - pendvertex->viewz-pstartvertex->viewz - - pendvertex->viewx+pstartvertex->viewx); - tempstartx = pstartvertex->viewx + - FixedMul((pendvertex->viewx-pstartvertex->viewx), - pwall->clippedtstart); - tempstartz = tempstartx; - } else { - // Clip the start point on the left side - pwall->clippedtstart = - FixedDiv(-pstartvertex->viewx-pstartvertex->viewz, - pendvertex->viewx+pendvertex->viewz - - pstartvertex->viewz-pstartvertex->viewx); - tempstartx = pstartvertex->viewx + - FixedMul((pendvertex->viewx-pstartvertex->viewx), - pwall->clippedtstart); - tempstartz = -tempstartx; - } - } - // See if the end point needs clipping - if ((tempendx > tempendz) || (tempendx < -tempendz)) { - // Clip the end point - if (tempendx > tempendz) { - // Clip the end point on the right side - pwall->clippedtend = - FixedDiv(pstartvertex->viewx-pstartvertex->viewz, - pendvertex->viewz-pstartvertex->viewz - - pendvertex->viewx+pstartvertex->viewx); - tempendx = pstartvertex->viewx + - FixedMul((pendvertex->viewx-pstartvertex->viewx), - pwall->clippedtend); - tempendz = tempendx; - } else { - // Clip the end point on the left side - pwall->clippedtend = - FixedDiv(-pstartvertex->viewx-pstartvertex->viewz, - pendvertex->viewx+pendvertex->viewz - - pstartvertex->viewz-pstartvertex->viewx); - tempendx = pstartvertex->viewx + - FixedMul((pendvertex->viewx-pstartvertex->viewx), - pwall->clippedtend); - tempendz = -tempendx; - } - } - tempstartwalltop = FixedMul((pwall->walltop - fxViewerY), - FIXEDPOINT(PROJECTION_RATIO)); - tempendwalltop = tempstartwalltop; - tempstartwallbottom = FixedMul((pwall->wallbottom-fxViewerY), - FIXEDPOINT(PROJECTION_RATIO)); - tempendwallbottom = tempstartwallbottom; - // Partially clip in y (the rest is done later in 2D) - // Check for trivial accept - if ((tempstartwalltop > tempstartz) || - (tempstartwallbottom < -tempstartz) || - (tempendwalltop > tempendz) || - (tempendwallbottom < -tempendz)) { - // Not trivially unclipped; check for fully clipped - if ((tempstartwallbottom > tempstartz) && - (tempstartwalltop < -tempstartz) && - (tempendwallbottom > tempendz) && - (tempendwalltop < -tempendz)) { - // Outside view triangle, trivially clipped - goto NextWall; - } - // Partially clipped in Y; we'll do Y clipping at - // drawing time - } - // The wall is visible; mark it as such and project it. - // +1 on scaling because of bottom/right exclusive polygon - // filling - pwall->isVisible = 1; - pwall->screenxstart = - (FixedMulDiv(tempstartx, fxHalfDIBWidth+FIXEDPOINT(0.5), - tempstartz) + fxHalfDIBWidth + FIXEDPOINT(0.5)); - pwall->screenytopstart = - (FixedMulDiv(tempstartwalltop, - fxHalfDIBHeight + FIXEDPOINT(0.5), tempstartz) + - fxHalfDIBHeight + FIXEDPOINT(0.5)); - pwall->screenybottomstart = - (FixedMulDiv(tempstartwallbottom, - fxHalfDIBHeight + FIXEDPOINT(0.5), tempstartz) + - fxHalfDIBHeight + FIXEDPOINT(0.5)); - pwall->screenxend = - (FixedMulDiv(tempendx, fxHalfDIBWidth+FIXEDPOINT(0.5), - tempendz) + fxHalfDIBWidth + FIXEDPOINT(0.5)); - pwall->screenytopend = - (FixedMulDiv(tempendwalltop, - fxHalfDIBHeight + FIXEDPOINT(0.5), tempendz) + - fxHalfDIBHeight + FIXEDPOINT(0.5)); - pwall->screenybottomend = - (FixedMulDiv(tempendwallbottom, - fxHalfDIBHeight + FIXEDPOINT(0.5), tempendz) + - fxHalfDIBHeight + FIXEDPOINT(0.5)); - NextWall: - pwall++; - } - } - // Walk the tree back to front; backface cull whenever possible, - // and draw front-facing walls in back-to-front order. - void DrawWallsBackToFront() - { - NODE *pFarChildren, *pNearChildren, *pwall; - NODE *pendingnodes[MAX_NUM_NODES]; - NODE **pendingstackptr; - POINT2INT apoint[4]; - pwall = pnodelist; - pendingnodes[0] = (NODE *)NULL; - pendingstackptr = pendingnodes + 1; - for (;;) { - for (;;) { - // Descend as far as possible toward the back, - // 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 - // 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 - // children first - pFarChildren = pwall->backtree; - } else { - // We're on the back side of this wall, do the front - // children first - pFarChildren = pwall->fronttree; - } - if (pFarChildren == NULL) - break; - *pendingstackptr = pwall; - pendingstackptr++; - pwall = pFarChildren; - } - for (;;) { - // See if the wall is even visible - if (pwall->isVisible) { - // See if we can backface cull this wall - if (pwall->screenxstart < pwall->screenxend) { - // Draw the wall - apoint[0].x = FIXTOINT(pwall->screenxstart); - apoint[1].x = FIXTOINT(pwall->screenxstart); - apoint[2].x = FIXTOINT(pwall->screenxend); - apoint[3].x = FIXTOINT(pwall->screenxend); - apoint[0].y = FIXTOINT(pwall->screenytopstart); - apoint[1].y = FIXTOINT(pwall->screenybottomstart); - apoint[2].y = FIXTOINT(pwall->screenybottomend); - apoint[3].y = FIXTOINT(pwall->screenytopend); - FillConvexPolygon(apoint, pwall->color); - } - } - // 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 - // 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 - // 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 - // front children now - pNearChildren = pwall->fronttree; - } else { - // We're on the back side of this wall, do the back - // children now - pNearChildren = pwall->backtree; - } - // Walk the near subtree of this wall - if (pNearChildren != NULL) - goto WalkNearTree; - // Pop the last-pushed wall - pendingstackptr—; - pwall = *pendingstackptr; - if (pwall == NULL) - goto NodesDone; - } - WalkNearTree: - pwall = pNearChildren; - } - NodesDone: - ; - } - // Render the current state of the world to the screen. - void UpdateWorld() - { - HPALETTE holdpal; - HDC hdcScreen, hdcDIBSection; - HBITMAP holdbitmap; - // Draw the frame - UpdateViewPos(); - memset(pDIB, 0, DIBPitch*DIBHeight); // clear frame - TransformVertices(); - ClipWalls(); - DrawWallsBackToFront(); - // We've drawn the frame; copy it to the screen - hdcScreen = GetDC(hwndOutput); - holdpal = SelectPalette(hdcScreen, hpalDIB, FALSE); - RealizePalette(hdcScreen); - hdcDIBSection = CreateCompatibleDC(hdcScreen); - holdbitmap = SelectObject(hdcDIBSection, hDIBSection); - BitBlt(hdcScreen, 0, 0, DIBWidth, DIBHeight, hdcDIBSection, - 0, 0, SRCCOPY); - SelectPalette(hdcScreen, holdpal, FALSE); - ReleaseDC(hwndOutput, hdcScreen); - SelectObject(hdcDIBSection, holdbitmap); - ReleaseDC(hwndOutput, hdcDIBSection); - iteration++; - } +```c +/* Core renderer for Win32 program to demonstrate drawing from a 2-D + BSP tree; illustrate the use of BSP trees for surface visibility. + UpdateWorld() is the top-level function in this module. + Full source code for the BSP-based renderer, and for the + accompanying BSP compiler, may be downloaded from + ftp.idsoftware.com/mikeab, in the file ddjbsp2.zip. + Tested with VC++ 2.0 running on Windows NT 3.5. */ +#define FIXEDPOINT(x) ((FIXEDPOINT)(((long)x)*((long)0x10000))) +#define FIXTOINT(x) ((int)(x >> 16)) +#define ANGLE(x) ((long)x) +#define STANDARD_SPEED (FIXEDPOINT(20)) +#define STANDARD_ROTATION (ANGLE(4)) +#define MAX_NUM_NODES 2000 +#define MAX_NUM_EXTRA_VERTICES 2000 +#define WORLD_MIN_X (FIXEDPOINT(-16000)) +#define WORLD_MAX_X (FIXEDPOINT(16000)) +#define WORLD_MIN_Y (FIXEDPOINT(-16000)) +#define WORLD_MAX_Y (FIXEDPOINT(16000)) +#define WORLD_MIN_Z (FIXEDPOINT(-16000)) +#define WORLD_MAX_Z (FIXEDPOINT(16000)) +#define PROJECTION_RATIO (2.0/1.0) // controls field of view; the + // bigger this is, the narrower the field of view +typedef long FIXEDPOINT; +typedef struct _VERTEX { + FIXEDPOINT x, z, viewx, viewz; +} VERTEX, *PVERTEX; +typedef struct _POINT2 { FIXEDPOINT x, z; } POINT2, *PPOINT2; +typedef struct _POINT2INT { int x; int y; } POINT2INT, *PPOINT2INT; +typedef long ANGLE; // angles are stored in degrees +typedef struct _NODE { + VERTEX *pstartvertex, *pendvertex; + FIXEDPOINT walltop, wallbottom, tstart, tend; + FIXEDPOINT clippedtstart, clippedtend; + struct _NODE *fronttree, *backtree; + int color, isVisible; + FIXEDPOINT screenxstart, screenxend; + FIXEDPOINT screenytopstart, screenybottomstart; + FIXEDPOINT screenytopend, screenybottomend; +} NODE, *PNODE; +char * pDIB; // pointer to DIB section we'll draw into +HBITMAP hDIBSection; // handle of DIB section +HPALETTE hpalDIB; +int iteration = 0, WorldIsRunning = 1; +HWND hwndOutput; +int DIBWidth, DIBHeight, DIBPitch, numvertices, numnodes; +FIXEDPOINT fxHalfDIBWidth, fxHalfDIBHeight; +VERTEX *pvertexlist, *pextravertexlist; +NODE *pnodelist; +POINT2 currentlocation, currentdirection, currentorientation; +ANGLE currentangle; +FIXEDPOINT currentspeed, fxViewerY, currentYSpeed; +FIXEDPOINT FrontClipPlane = FIXEDPOINT(10); +FIXEDPOINT FixedMul(FIXEDPOINT x, FIXEDPOINT y); +FIXEDPOINT FixedDiv(FIXEDPOINT x, FIXEDPOINT y); +FIXEDPOINT FixedSin(ANGLE angle), FixedCos(ANGLE angle); +extern int FillConvexPolygon(POINT2INT * VertexPtr, int Color); +// Returns nonzero if a wall is facing the viewer, 0 else. +int WallFacingViewer(NODE * pwall) +{ + FIXEDPOINT viewxstart = pwall->pstartvertex->viewx; + FIXEDPOINT viewzstart = pwall->pstartvertex->viewz; + FIXEDPOINT viewxend = pwall->pendvertex->viewx; + FIXEDPOINT viewzend = pwall->pendvertex->viewz; + int Temp; +/* // equivalent C code + if (( ((pwall->pstartvertex->viewx >> 16) * + ((pwall->pendvertex->viewz - + pwall->pstartvertex->viewz) >> 16)) + + ((pwall->pstartvertex->viewz >> 16) * + ((pwall->pstartvertex->viewx - + pwall->pendvertex->viewx) >> 16)) ) + < 0) + return(1); + else + return(0); +*/ + _asm { + mov eax,viewzend + sub eax,viewzstart + imul viewxstart + mov ecx,edx + mov ebx,eax + mov eax,viewxstart + sub eax,viewxend + imul viewzstart + add eax,ebx + adc edx,ecx + mov eax,0 + jns short WFVDone + inc eax +WFVDone: + mov Temp,eax + } + return(Temp); +} +// Update the viewpoint position as needed. +void UpdateViewPos() +{ + if (currentspeed != 0) { + currentlocation.x += FixedMul(currentdirection.x, + currentspeed); + if (currentlocation.x <= WORLD_MIN_X) + currentlocation.x = WORLD_MIN_X; + if (currentlocation.x >= WORLD_MAX_X) + currentlocation.x = WORLD_MAX_X - 1; + currentlocation.z += FixedMul(currentdirection.z, + currentspeed); + if (currentlocation.z <= WORLD_MIN_Z) + currentlocation.z = WORLD_MIN_Z; + if (currentlocation.z >= WORLD_MAX_Z) + currentlocation.z = WORLD_MAX_Z - 1; + } + if (currentYSpeed != 0) { + fxViewerY += currentYSpeed; + if (fxViewerY <= WORLD_MIN_Y) + fxViewerY = WORLD_MIN_Y; + if (fxViewerY >= WORLD_MAX_Y) + fxViewerY = WORLD_MAX_Y - 1; + } +} +// Transform all vertices into viewspace. +void TransformVertices() +{ + VERTEX *pvertex; + FIXEDPOINT tempx, tempz; + int vertex; + pvertex = pvertexlist; + for (vertex = 0; vertex < numvertices; vertex++) { + // Translate the vertex according to the viewpoint + tempx = pvertex->x - currentlocation.x; + tempz = pvertex->z - currentlocation.z; + // Rotate the vertex so viewpoint is looking down z axis + pvertex->viewx = FixedMul(FixedMul(tempx, + currentorientation.z) + + FixedMul(tempz, -currentorientation.x), + FIXEDPOINT(PROJECTION_RATIO)); + pvertex->viewz = FixedMul(tempx, currentorientation.x) + + FixedMul(tempz, currentorientation.z); + pvertex++; + } +} +// 3-D clip all walls. If any part of each wall is still visible, +// transform to perspective viewspace. +void ClipWalls() +{ + NODE *pwall; + int wall; + FIXEDPOINT tempstartx, tempendx, tempstartz, tempendz; + FIXEDPOINT tempstartwalltop, tempstartwallbottom; + FIXEDPOINT tempendwalltop, tempendwallbottom; + VERTEX *pstartvertex, *pendvertex; + VERTEX *pextravertex = pextravertexlist; + pwall = pnodelist; + for (wall = 0; wall < numnodes; wall++) { + // Assume the wall won't be visible + pwall->isVisible = 0; + // Generate the wall endpoints, accounting for t values and + // clipping + // Calculate the viewspace coordinates for this wall + pstartvertex = pwall->pstartvertex; + pendvertex = pwall->pendvertex; + // Look for z clipping first + // Calculate start and end z coordinates for this wall + if (pwall->tstart == FIXEDPOINT(0)) + tempstartz = pstartvertex->viewz; + else + tempstartz = pstartvertex->viewz + + FixedMul((pendvertex->viewz-pstartvertex->viewz), + pwall->tstart); + if (pwall->tend == FIXEDPOINT(1)) + tempendz = pendvertex->viewz; + else + tempendz = pstartvertex->viewz + + FixedMul((pendvertex->viewz-pstartvertex->viewz), + pwall->tend); + // Clip to the front plane + if (tempendz < FrontClipPlane) { + if (tempstartz < FrontClipPlane) { + // Fully front-clipped + goto NextWall; + } else { + pwall->clippedtstart = pwall->tstart; + // Clip the end point to the front clip plane + pwall->clippedtend = + FixedDiv(pstartvertex->viewz - FrontClipPlane, + pstartvertex->viewz-pendvertex->viewz); + tempendz = pstartvertex->viewz + + FixedMul((pendvertex->viewz-pstartvertex->viewz), + pwall->clippedtend); + } + } else { + pwall->clippedtend = pwall->tend; + if (tempstartz < FrontClipPlane) { + // Clip the start point to the front clip plane + pwall->clippedtstart = + FixedDiv(FrontClipPlane - pstartvertex->viewz, + pendvertex->viewz-pstartvertex->viewz); + tempstartz = pstartvertex->viewz + + FixedMul((pendvertex->viewz-pstartvertex->viewz), + pwall->clippedtstart); + } else { + pwall->clippedtstart = pwall->tstart; + } + } + // Calculate x coordinates + if (pwall->clippedtstart == FIXEDPOINT(0)) + tempstartx = pstartvertex->viewx; + else + tempstartx = pstartvertex->viewx + + FixedMul((pendvertex->viewx-pstartvertex->viewx), + pwall->clippedtstart); + if (pwall->clippedtend == FIXEDPOINT(1)) + tempendx = pendvertex->viewx; + else + tempendx = pstartvertex->viewx + + FixedMul((pendvertex->viewx-pstartvertex->viewx), + pwall->clippedtend); + // Clip in x as needed + if ((tempstartx > tempstartz) || (tempstartx < -tempstartz)) { + // The start point is outside the view triangle in x; + // perform a quick test for trivial rejection by seeing if + // the end point is outside the view triangle on the same + // side as the start point + if (((tempstartx>tempstartz) && (tempendx>tempendz)) || + ((tempstartx<-tempstartz) && (tempendx<-tempendz))) + // Fully clipped—trivially reject + goto NextWall; + // Clip the start point + if (tempstartx > tempstartz) { + // Clip the start point on the right side + pwall->clippedtstart = + FixedDiv(pstartvertex->viewx-pstartvertex->viewz, + pendvertex->viewz-pstartvertex->viewz - + pendvertex->viewx+pstartvertex->viewx); + tempstartx = pstartvertex->viewx + + FixedMul((pendvertex->viewx-pstartvertex->viewx), + pwall->clippedtstart); + tempstartz = tempstartx; + } else { + // Clip the start point on the left side + pwall->clippedtstart = + FixedDiv(-pstartvertex->viewx-pstartvertex->viewz, + pendvertex->viewx+pendvertex->viewz - + pstartvertex->viewz-pstartvertex->viewx); + tempstartx = pstartvertex->viewx + + FixedMul((pendvertex->viewx-pstartvertex->viewx), + pwall->clippedtstart); + tempstartz = -tempstartx; + } + } + // See if the end point needs clipping + if ((tempendx > tempendz) || (tempendx < -tempendz)) { + // Clip the end point + if (tempendx > tempendz) { + // Clip the end point on the right side + pwall->clippedtend = + FixedDiv(pstartvertex->viewx-pstartvertex->viewz, + pendvertex->viewz-pstartvertex->viewz - + pendvertex->viewx+pstartvertex->viewx); + tempendx = pstartvertex->viewx + + FixedMul((pendvertex->viewx-pstartvertex->viewx), + pwall->clippedtend); + tempendz = tempendx; + } else { + // Clip the end point on the left side + pwall->clippedtend = + FixedDiv(-pstartvertex->viewx-pstartvertex->viewz, + pendvertex->viewx+pendvertex->viewz - + pstartvertex->viewz-pstartvertex->viewx); + tempendx = pstartvertex->viewx + + FixedMul((pendvertex->viewx-pstartvertex->viewx), + pwall->clippedtend); + tempendz = -tempendx; + } + } + tempstartwalltop = FixedMul((pwall->walltop - fxViewerY), + FIXEDPOINT(PROJECTION_RATIO)); + tempendwalltop = tempstartwalltop; + tempstartwallbottom = FixedMul((pwall->wallbottom-fxViewerY), + FIXEDPOINT(PROJECTION_RATIO)); + tempendwallbottom = tempstartwallbottom; + // Partially clip in y (the rest is done later in 2D) + // Check for trivial accept + if ((tempstartwalltop > tempstartz) || + (tempstartwallbottom < -tempstartz) || + (tempendwalltop > tempendz) || + (tempendwallbottom < -tempendz)) { + // Not trivially unclipped; check for fully clipped + if ((tempstartwallbottom > tempstartz) && + (tempstartwalltop < -tempstartz) && + (tempendwallbottom > tempendz) && + (tempendwalltop < -tempendz)) { + // Outside view triangle, trivially clipped + goto NextWall; + } + // Partially clipped in Y; we'll do Y clipping at + // drawing time + } + // The wall is visible; mark it as such and project it. + // +1 on scaling because of bottom/right exclusive polygon + // filling + pwall->isVisible = 1; + pwall->screenxstart = + (FixedMulDiv(tempstartx, fxHalfDIBWidth+FIXEDPOINT(0.5), + tempstartz) + fxHalfDIBWidth + FIXEDPOINT(0.5)); + pwall->screenytopstart = + (FixedMulDiv(tempstartwalltop, + fxHalfDIBHeight + FIXEDPOINT(0.5), tempstartz) + + fxHalfDIBHeight + FIXEDPOINT(0.5)); + pwall->screenybottomstart = + (FixedMulDiv(tempstartwallbottom, + fxHalfDIBHeight + FIXEDPOINT(0.5), tempstartz) + + fxHalfDIBHeight + FIXEDPOINT(0.5)); + pwall->screenxend = + (FixedMulDiv(tempendx, fxHalfDIBWidth+FIXEDPOINT(0.5), + tempendz) + fxHalfDIBWidth + FIXEDPOINT(0.5)); + pwall->screenytopend = + (FixedMulDiv(tempendwalltop, + fxHalfDIBHeight + FIXEDPOINT(0.5), tempendz) + + fxHalfDIBHeight + FIXEDPOINT(0.5)); + pwall->screenybottomend = + (FixedMulDiv(tempendwallbottom, + fxHalfDIBHeight + FIXEDPOINT(0.5), tempendz) + + fxHalfDIBHeight + FIXEDPOINT(0.5)); +NextWall: + pwall++; + } +} +// Walk the tree back to front; backface cull whenever possible, +// and draw front-facing walls in back-to-front order. +void DrawWallsBackToFront() +{ + NODE *pFarChildren, *pNearChildren, *pwall; + NODE *pendingnodes[MAX_NUM_NODES]; + NODE **pendingstackptr; + POINT2INT apoint[4]; + pwall = pnodelist; + pendingnodes[0] = (NODE *)NULL; + pendingstackptr = pendingnodes + 1; + for (;;) { + for (;;) { + // Descend as far as possible toward the back, + // 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 + // 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 + // children first + pFarChildren = pwall->backtree; + } else { + // We're on the back side of this wall, do the front + // children first + pFarChildren = pwall->fronttree; + } + if (pFarChildren == NULL) + break; + *pendingstackptr = pwall; + pendingstackptr++; + pwall = pFarChildren; + } + for (;;) { + // See if the wall is even visible + if (pwall->isVisible) { + // See if we can backface cull this wall + if (pwall->screenxstart < pwall->screenxend) { + // Draw the wall + apoint[0].x = FIXTOINT(pwall->screenxstart); + apoint[1].x = FIXTOINT(pwall->screenxstart); + apoint[2].x = FIXTOINT(pwall->screenxend); + apoint[3].x = FIXTOINT(pwall->screenxend); + apoint[0].y = FIXTOINT(pwall->screenytopstart); + apoint[1].y = FIXTOINT(pwall->screenybottomstart); + apoint[2].y = FIXTOINT(pwall->screenybottomend); + apoint[3].y = FIXTOINT(pwall->screenytopend); + FillConvexPolygon(apoint, pwall->color); + } + } + // 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 + // 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 + // 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 + // front children now + pNearChildren = pwall->fronttree; + } else { + // We're on the back side of this wall, do the back + // children now + pNearChildren = pwall->backtree; + } + // Walk the near subtree of this wall + if (pNearChildren != NULL) + goto WalkNearTree; + // Pop the last-pushed wall + pendingstackptr—; + pwall = *pendingstackptr; + if (pwall == NULL) + goto NodesDone; + } +WalkNearTree: + pwall = pNearChildren; + } +NodesDone: +; +} +// Render the current state of the world to the screen. +void UpdateWorld() +{ + HPALETTE holdpal; + HDC hdcScreen, hdcDIBSection; + HBITMAP holdbitmap; + // Draw the frame + UpdateViewPos(); + memset(pDIB, 0, DIBPitch*DIBHeight); // clear frame + TransformVertices(); + ClipWalls(); + DrawWallsBackToFront(); + // We've drawn the frame; copy it to the screen + hdcScreen = GetDC(hwndOutput); + holdpal = SelectPalette(hdcScreen, hpalDIB, FALSE); + RealizePalette(hdcScreen); + hdcDIBSection = CreateCompatibleDC(hdcScreen); + holdbitmap = SelectObject(hdcDIBSection, hDIBSection); + BitBlt(hdcScreen, 0, 0, DIBWidth, DIBHeight, hdcDIBSection, + 0, 0, SRCCOPY); + SelectPalette(hdcScreen, holdpal, FALSE); + ReleaseDC(hwndOutput, hdcScreen); + SelectObject(hdcDIBSection, holdbitmap); + ReleaseDC(hwndOutput, hdcDIBSection); + iteration++; +} +``` diff --git a/63-02.md b/63-02.md index addb952..8d7eabd 100644 --- a/63-02.md +++ b/63-02.md @@ -23,9 +23,11 @@ instructions, an FDIV and two integer instructions, executing at the 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 +```nasm +FDIV ST(0),ST(1) +ADD EAX,ECX +INC EDX +``` There's an important limitation, though; if the instruction stream following the FDIV reaches a FP instruction (or an IMUL), then that @@ -38,8 +40,10 @@ 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 of an arithmetic instruction until an FST of that value can begin, so - FMUL ST(0),ST(1) - FST [temp] +```nasm +FMUL ST(0),ST(1) +FST [temp] +``` 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 @@ -57,10 +61,12 @@ to start on the next cycle, though: FADD, FSUB, and FMUL are pipelined, but FST and FDIV are not. (FLD executes in a single cycle, so pipelining is not an issue.) Thus, in the code sequence - FADD1 - FSUB - FADD2 - FMUL +```nasm +FADD1 +FSUB +FADD2 +FMUL +``` 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 @@ -77,29 +83,37 @@ throughput—the rate at which the FPU can start new instructions—is 1 cycle. An exception is that the FPU is capable of starting an FMUL only every 2 cycles, so between these two instructions - FMUL ST(1),ST(0) - FMUL ST(2),ST(0) +```nasm +FMUL ST(1),ST(0) +FMUL ST(2),ST(0) +``` 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) +```nasm +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 its operands are available. The FPU can reach a throughput of 1 cycle per instruction on this code - FADD ST(1),ST(0) - FLD [temp] - FSUB ST(1),ST(0) +```nasm +FADD ST(1),ST(0) +FLD [temp] +FSUB ST(1),ST(0) +``` because neither the FLD nor the FSUB needs the result from the FADD. Consider, however - FADD ST(0),ST(2) - FSUB ST(0),ST(1) +```nasm +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 start until FADD has completed, so there are 2 stall cycles between the @@ -134,15 +148,17 @@ multiplications, without incurring any stalls, as shown in Listing 63.1. **Listing 63.1 L63-1.ASM** - ; use of fxch to allow addition of first two; products to start while third : multiplication finishes - fld [vec0+0] ;starts & ends on cycle 0 - fmul [vec1+0] ;starts on cycle 1 - fld [vec0+4] ;starts & ends on cycle 2 - fmul [vec1+4] ;starts on cycle 3 - fld [vec0+8] ;starts & ends on cycle 4 - fmul [vec1+8] ;starts on cycle 5 - fxch st(1) ;no cost - faddp st(2),st(0) ;starts on cycle 6 +```nasm +; use of fxch to allow addition of first two; products to start while third : multiplication finishes + fld [vec0+0] ;starts & ends on cycle 0 + fmul [vec1+0] ;starts on cycle 1 + fld [vec0+4] ;starts & ends on cycle 2 + fmul [vec1+4] ;starts on cycle 3 + fld [vec0+8] ;starts & ends on cycle 4 + fmul [vec1+8] ;starts on cycle 5 + fxch st(1) ;no cost + faddp st(2),st(0) ;starts on cycle 6 +``` ### The Dot Product {#Heading7} diff --git a/63-03.md b/63-03.md index a75e940..ac6f178 100644 --- a/63-03.md +++ b/63-03.md @@ -12,37 +12,41 @@ pages: 1170-1173 **Listing 63.2 1 L63-2.ASM** - ; unoptimized dot product; 17 cycles - fld [vec0+0] ;starts & ends on cycle 0 - fmul [vec1+0] ;starts on cycle 1 - fld [vec0+4] ;starts & ends on cycle 2 - fmul [vec1+4] ;starts on cycle 3 - fld [vec0+8] ;starts & ends on cycle 4 - fmul [vec1+8] ;starts on cycle 5 - ;stalls for cycles 6-7 - faddp st(1),st(0) ;starts on cycle 8 - ;stalls for cycles 9-10 - faddp st(1),st(0) ;starts on cycle 11 - ;stalls for cycles 12-14 - fstp [dot] ;starts on cycle 15, - ; ends on cycle 16 +```nasm +; unoptimized dot product; 17 cycles + fld [vec0+0] ;starts & ends on cycle 0 + fmul [vec1+0] ;starts on cycle 1 + fld [vec0+4] ;starts & ends on cycle 2 + fmul [vec1+4] ;starts on cycle 3 + fld [vec0+8] ;starts & ends on cycle 4 + fmul [vec1+8] ;starts on cycle 5 + ;stalls for cycles 6-7 + faddp st(1),st(0) ;starts on cycle 8 + ;stalls for cycles 9-10 + faddp st(1),st(0) ;starts on cycle 11 + ;stalls for cycles 12-14 + fstp [dot] ;starts on cycle 15, + ; ends on cycle 16 +``` **Listing 63.3 L63-3.ASM** - ;optimized dot product; 15 cycles - fld [vec0+0] ;starts & ends on cycle 0 - fmul [vec1+0] ;starts on cycle 1 - fld [vec0+4] ;starts & ends on cycle 2 - fmul [vec1+4] ;starts on cycle 3 - fld [vec0+8] ;starts & ends on cycle 4 - fmul [vec1+8] ;starts on cycle 5 - fxch st(1) ;no cost - faddp st(2),st(0) ;starts on cycle 6 - ;stalls for cycles 7-8 - faddp st(1),st(0) ;starts on cycle 9 - ;stalls for cycles 10-12 - fstp [dot] ;starts on cycle 13, - ; ends on cycle 14 +```nasm +;optimized dot product; 15 cycles + fld [vec0+0] ;starts & ends on cycle 0 + fmul [vec1+0] ;starts on cycle 1 + fld [vec0+4] ;starts & ends on cycle 2 + fmul [vec1+4] ;starts on cycle 3 + fld [vec0+8] ;starts & ends on cycle 4 + fmul [vec1+8] ;starts on cycle 5 + fxch st(1) ;no cost + faddp st(2),st(0) ;starts on cycle 6 + ;stalls for cycles 7-8 + faddp st(1),st(0) ;starts on cycle 9 + ;stalls for cycles 10-12 + fstp [dot] ;starts on cycle 13, + ; ends on cycle 14 +``` ### The Cross Product {#Heading8} @@ -56,35 +60,37 @@ losing 15 cycles to stalls. **Listing 63.4 L63-4.ASM** - ;unoptimized cross product; 36 cycles - fld [vec0+4] ;starts & ends on cycle 0 - fmul [vec1+8] ;starts on cycle 1 - fld [vec0+8] ;starts & ends on cycle 2 - fmul [vec1+4] ;starts on cycle 3 - ;stalls for cycles 4-5 - fsubrp st(1),st(0) ;starts on cycle 6 - ;stalls for cycles 7-9 - fstp [vec2+0] ;starts on cycle 10, - ; ends on cycle 11 - fld [vec0+8] ;starts & ends on cycle 12 - fmul [vec1+0] ;starts on cycle 13 - fld [vec0+0] ;starts & ends on cycle 14 - fmul [vec1+8] ;starts on cycle 15 - ;stalls for cycles 16-17 - fsubrp st(1),st(0) ;starts on cycle 18 - ;stalls for cycles 19-21 - fstp [vec2+4] ;starts on cycle 22, - ; ends on cycle 23 +```nasm +;unoptimized cross product; 36 cycles + fld [vec0+4] ;starts & ends on cycle 0 + fmul [vec1+8] ;starts on cycle 1 + fld [vec0+8] ;starts & ends on cycle 2 + fmul [vec1+4] ;starts on cycle 3 + ;stalls for cycles 4-5 + fsubrp st(1),st(0) ;starts on cycle 6 + ;stalls for cycles 7-9 + fstp [vec2+0] ;starts on cycle 10, + ; ends on cycle 11 + fld [vec0+8] ;starts & ends on cycle 12 + fmul [vec1+0] ;starts on cycle 13 + fld [vec0+0] ;starts & ends on cycle 14 + fmul [vec1+8] ;starts on cycle 15 + ;stalls for cycles 16-17 + fsubrp st(1),st(0) ;starts on cycle 18 + ;stalls for cycles 19-21 + fstp [vec2+4] ;starts on cycle 22, + ; ends on cycle 23 - fld [vec0+0] ;starts & ends on cycle 24 - fmul [vec1+4] ;starts on cycle 25 - fld [vec0+4] ;starts & ends on cycle 26 - fmul [vec1+0] ;starts on cycle 27 - ;stalls for cycles 28-29 - fsubrp st(1),st(0) ;starts on cycle 30 - ;stalls for cycles 31-33 - fstp [vec2+8] ;starts on cycle 34, - ; ends on cycle 35 + fld [vec0+0] ;starts & ends on cycle 24 + fmul [vec1+4] ;starts on cycle 25 + fld [vec0+4] ;starts & ends on cycle 26 + fmul [vec1+0] ;starts on cycle 27 + ;stalls for cycles 28-29 + fsubrp st(1),st(0) ;starts on cycle 30 + ;stalls for cycles 31-33 + 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 because with six inputs and one output, it was impossible to interleave @@ -101,31 +107,33 @@ of properly managing the Pentium's FP pipeline. **Listing 63.5 L63-5.ASM** - ;optimized cross product; 22 cycles - fld [vec0+4] ;starts & ends on cycle 0 - fmul [vec1+8] ;starts on cycle 1 - fld [vec0+8] ;starts & ends on cycle 2 - fmul [vec1+0] ;starts on cycle 3 - fld [vec0+0] ;starts & ends on cycle 4 - fmul [vec1+4] ;starts on cycle 5 - fld [vec0+8] ;starts & ends on cycle 6 - fmul [vec1+4] ;starts on cycle 7 - fld [vec0+0] ;starts & ends on cycle 8 - fmul [vec1+8] ;starts on cycle 9 - fld [vec0+4] ;starts & ends on cycle 10 - fmul [vec1+0] ;starts on cycle 11 - fxch st(2) ;no cost - fsubrp st(5),st(0) ;starts on cycle 12 - fsubrp st(3),st(0) ;starts on cycle 13 - fsubrp st(1),st(0) ;starts on cycle 14 - fxch st(2) ;no cost - ;stalls for cycle 15 - fstp [vec2+0] ;starts on cycle 16, - ; ends on cycle 17 - fstp [vec2+4] ;starts on cycle 18, - ; ends on cycle 19 - fstp [vec2+8] ;starts on cycle 20, - ; ends on cycle 21 +```nasm +;optimized cross product; 22 cycles + fld [vec0+4] ;starts & ends on cycle 0 + fmul [vec1+8] ;starts on cycle 1 + fld [vec0+8] ;starts & ends on cycle 2 + fmul [vec1+0] ;starts on cycle 3 + fld [vec0+0] ;starts & ends on cycle 4 + fmul [vec1+4] ;starts on cycle 5 + fld [vec0+8] ;starts & ends on cycle 6 + fmul [vec1+4] ;starts on cycle 7 + fld [vec0+0] ;starts & ends on cycle 8 + fmul [vec1+8] ;starts on cycle 9 + fld [vec0+4] ;starts & ends on cycle 10 + fmul [vec1+0] ;starts on cycle 11 + fxch st(2) ;no cost + fsubrp st(5),st(0) ;starts on cycle 12 + fsubrp st(3),st(0) ;starts on cycle 13 + fsubrp st(1),st(0) ;starts on cycle 14 + fxch st(2) ;no cost + ;stalls for cycle 15 + fstp [vec2+0] ;starts on cycle 16, + ; ends on cycle 17 + fstp [vec2+4] ;starts on cycle 18, + ; ends on cycle 19 + fstp [vec2+8] ;starts on cycle 20, + ; ends on cycle 21 +``` ### Transformation {#Heading9} @@ -140,12 +148,10 @@ transformation.) Transformation is calculated as: - - - - - - - - v1 m11 m12 m13 m14 u1 - v2 = m21 m22 m23 m24 u2 - v3 m31 m32 m33 m34 u3 - 1 0 0 0 1 1 - - - - - - - + v1 m11 m12 m13 m14 u1 + v2 = m21 m22 m23 m24 u2 + v3 m31 m32 m33 m34 u3 + 1 0 0 0 1 1 or diff --git a/63-04.md b/63-04.md index 8e0760c..09a8374 100644 --- a/63-04.md +++ b/63-04.md @@ -29,46 +29,48 @@ certainly feasible; at a frame rate of 30 Hz, that's an impressive **Listing 63.6 L63-6.ASM** - ;optimized transformation: 34 cycles - fld [vec0+0] ;starts & ends on cycle 0 - fmul [matrix+0] ;starts on cycle 1 - fld [vec0+0] ;starts & ends on cycle 2 - fmul [matrix+16] ;starts on cycle 3 - fld [vec0+0] ;starts & ends on cycle 4 - fmul [matrix+32] ;starts on cycle 5 - fld [vec0+4] ;starts & ends on cycle 6 - fmul [matrix+4] ;starts on cycle 7 - fld [vec0+4] ;starts & ends on cycle 8 - fmul [matrix+20] ;starts on cycle 9 - fld [vec0+4] ;starts & ends on cycle 10 - fmul [matrix+36] ;starts on cycle 11 - fxch st(2) ;no cost - faddp st(5),st(0) ;starts on cycle 12 - faddp st(3),st(0) ;starts on cycle 13 - faddp st(1),st(0) ;starts on cycle 14 - fld [vec0+8] ;starts & ends on cycle 15 - fmul [matrix+8] ;starts on cycle 16 - fld [vec0+8] ;starts & ends on cycle 17 - fmul [matrix+24] ;starts on cycle 18 - fld [vec0+8] ;starts & ends on cycle 19 - fmul [matrix+40] ;starts on cycle 20 - fxch st(2) ;no cost - faddp st(5),st(0) ;starts on cycle 21 - faddp st(3),st(0) ;starts on cycle 22 - faddp st(1),st(0) ;starts on cycle 23 - fxch st(2) ;no cost - fadd [matrix+12] ;starts on cycle 24 - fxch st(1) ;starts on cycle 25 - fadd [matrix+28] ;starts on cycle 26 - fxch st(2) ;no cost - fadd [matrix+44] ;starts on cycle 27 - fxch st(1) ;no cost - fstp [vec1+0] ;starts on cycle 28, - ; ends on cycle 29 - fstp [vec1+8] ;starts on cycle 30, - ; ends on cycle 31 - fstp [vec1+4] ;starts on cycle 32, - ; ends on cycle 33 +```nasm + ;optimized transformation: 34 cycles + fld [vec0+0] ;starts & ends on cycle 0 + fmul [matrix+0] ;starts on cycle 1 + fld [vec0+0] ;starts & ends on cycle 2 + fmul [matrix+16] ;starts on cycle 3 + fld [vec0+0] ;starts & ends on cycle 4 + fmul [matrix+32] ;starts on cycle 5 + fld [vec0+4] ;starts & ends on cycle 6 + fmul [matrix+4] ;starts on cycle 7 + fld [vec0+4] ;starts & ends on cycle 8 + fmul [matrix+20] ;starts on cycle 9 + fld [vec0+4] ;starts & ends on cycle 10 + fmul [matrix+36] ;starts on cycle 11 + fxch st(2) ;no cost + faddp st(5),st(0) ;starts on cycle 12 + faddp st(3),st(0) ;starts on cycle 13 + faddp st(1),st(0) ;starts on cycle 14 + fld [vec0+8] ;starts & ends on cycle 15 + fmul [matrix+8] ;starts on cycle 16 + fld [vec0+8] ;starts & ends on cycle 17 + fmul [matrix+24] ;starts on cycle 18 + fld [vec0+8] ;starts & ends on cycle 19 + fmul [matrix+40] ;starts on cycle 20 + fxch st(2) ;no cost + faddp st(5),st(0) ;starts on cycle 21 + faddp st(3),st(0) ;starts on cycle 22 + faddp st(1),st(0) ;starts on cycle 23 + fxch st(2) ;no cost + fadd [matrix+12] ;starts on cycle 24 + fxch st(1) ;starts on cycle 25 + fadd [matrix+28] ;starts on cycle 26 + fxch st(2) ;no cost + fadd [matrix+44] ;starts on cycle 27 + fxch st(1) ;no cost + fstp [vec1+0] ;starts on cycle 28, + ; ends on cycle 29 + fstp [vec1+8] ;starts on cycle 30, + ; ends on cycle 31 + fstp [vec1+4] ;starts on cycle 32, + ; ends on cycle 33 +``` ### Projection {#Heading10} diff --git a/65-02.md b/65-02.md index 96107fe..3091383 100644 --- a/65-02.md +++ b/65-02.md @@ -30,38 +30,40 @@ polygons, and planes are shown in Listing 65.1. **LISTING 65.1 L65\_1.h** - typedef struct { - double v[3]; - } point_t; +```c +typedef struct { + double v[3]; +} point_t; - typedef struct { - double x, y; - } point2D_t; +typedef struct { + double x, y; +} point2D_t; - typedef struct { - int color; - int numverts; - point_t verts[MAX_POLY_VERTS]; - } polygon_t; +typedef struct { + int color; + int numverts; + point_t verts[MAX_POLY_VERTS]; +} polygon_t; - typedef struct { - int color; - int numverts; - point2D_t verts[MAX_POLY_VERTS]; - } polygon2D_t; +typedef struct { + int color; + int numverts; + point2D_t verts[MAX_POLY_VERTS]; +} polygon2D_t; - typedef struct convexobject_s { - struct convexobject_s *pnext; - point_t center; - double vdist; - int numpolys; - polygon_t *ppoly; - } convexobject_t; +typedef struct convexobject_s { + struct convexobject_s *pnext; + point_t center; + double vdist; + int numpolys; + polygon_t *ppoly; +} convexobject_t; - typedef struct { - double distance; - point_t normal; - } plane_t; +typedef struct { + double distance; + point_t normal; +} plane_t; +``` 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 diff --git a/65-03.md b/65-03.md index 450d258..2391671 100644 --- a/65-03.md +++ b/65-03.md @@ -12,57 +12,59 @@ pages: 1199-1206 **LISTING 65.2 L65\_2.c** - int ClipToPlane(polygon_t *pin, plane_t *pplane, polygon_t *pout) +```c +int ClipToPlane(polygon_t *pin, plane_t *pplane, polygon_t *pout) +{ + int i, j, nextvert, curin, nextin; + double curdot, nextdot, scale; + point_t *pinvert, *poutvert; + + pinvert = pin->verts; + poutvert = pout->verts; + + curdot = DotProduct(pinvert, &pplane->normal); + curin = (curdot >= pplane->distance); + + for (i=0 ; inumverts ; i++) { - int i, j, nextvert, curin, nextin; - double curdot, nextdot, scale; - point_t *pinvert, *poutvert; + nextvert = (i + 1) % pin->numverts; - pinvert = pin->verts; - poutvert = pout->verts; + // Keep the current vertex if it's inside the plane + if (curin) + *poutvert++ = *pinvert; - curdot = DotProduct(pinvert, &pplane->normal); - curin = (curdot >= pplane->distance); + nextdot = DotProduct(&pin->verts[nextvert], &pplane->normal); + nextin = (nextdot >= pplane->distance); - for (i=0 ; inumverts ; i++) + // Add a clipped vertex if one end of the current edge is + // inside the plane and the other is outside + if (curin != nextin) { - nextvert = (i + 1) % pin->numverts; - - // Keep the current vertex if it's inside the plane - if (curin) - *poutvert++ = *pinvert; - - nextdot = DotProduct(&pin->verts[nextvert], &pplane->normal); - nextin = (nextdot >= pplane->distance); - - // Add a clipped vertex if one end of the current edge is - // inside the plane and the other is outside - if (curin != nextin) + scale = (pplane->distance - curdot) / + (nextdot - curdot); + for (j=0 ; j<3 ; j++) { - scale = (pplane->distance - curdot) / - (nextdot - curdot); - for (j=0 ; j<3 ; j++) - { - poutvert->v[j] = pinvert->v[j] + - ((pin->verts[nextvert].v[j] - pinvert->v[j]) * - scale); - } - poutvert++; + poutvert->v[j] = pinvert->v[j] + + ((pin->verts[nextvert].v[j] - pinvert->v[j]) * + scale); } - - curdot = nextdot; - curin = nextin; - pinvert++; + poutvert++; } - pout->numverts = poutvert - pout->verts; - if (pout->numverts < 3) - return 0; - - pout->color = pin->color; - return 1; + curdot = nextdot; + curin = nextin; + pinvert++; } + pout->numverts = poutvert - pout->verts; + if (pout->numverts < 3) + return 0; + + pout->color = pin->color; + return 1; +} +``` + 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 clipped to multiple planes by repeating the above process once for each @@ -92,309 +94,311 @@ is available on the CD-ROM in the file DDJCLIP.ZIP. **LISTING 65.3 L65\_3.c** - int DIBWidth, DIBHeight; - int DIBPitch; - double roll, pitch, yaw; - double currentspeed; - point_t currentpos; - double fieldofview, xcenter, ycenter; - double xscreenscale, yscreenscale, maxscale; - int numobjects; - double speedscale = 1.0; - plane_t frustumplanes[NUM_FRUSTUM_PLANES]; - double mroll[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; - double mpitch[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; - double myaw[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; - point_t vpn, vright, vup; - point_t xaxis = {1, 0, 0}; - point_t zaxis = {0, 0, 1}; - convexobject_t objecthead = {NULL, {0,0,0}, -999999.0}; +```c +int DIBWidth, DIBHeight; +int DIBPitch; +double roll, pitch, yaw; +double currentspeed; +point_t currentpos; +double fieldofview, xcenter, ycenter; +double xscreenscale, yscreenscale, maxscale; +int numobjects; +double speedscale = 1.0; +plane_t frustumplanes[NUM_FRUSTUM_PLANES]; +double mroll[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; +double mpitch[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; +double myaw[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; +point_t vpn, vright, vup; +point_t xaxis = {1, 0, 0}; +point_t zaxis = {0, 0, 1}; +convexobject_t objecthead = {NULL, {0,0,0}, -999999.0}; - // Project viewspace polygon vertices into screen coordinates. - // Note that the y axis goes up in worldspace and viewspace, but - // goes down in screenspace. - void ProjectPolygon (polygon_t *ppoly, polygon2D_t *ppoly2D) +// Project viewspace polygon vertices into screen coordinates. +// Note that the y axis goes up in worldspace and viewspace, but +// goes down in screenspace. +void ProjectPolygon (polygon_t *ppoly, polygon2D_t *ppoly2D) +{ + int i; + double zrecip; + + for (i=0 ; inumverts ; i++) { - int i; - double zrecip; - - for (i=0 ; inumverts ; i++) - { - zrecip = 1.0 / ppoly->verts[i].v[2]; - ppoly2D->verts[i].x = - ppoly->verts[i].v[0] * zrecip * maxscale + xcenter; - ppoly2D->verts[i].y = DIBHeight - - (ppoly->verts[i].v[1] * zrecip * maxscale + ycenter); - } - ppoly2D->color = ppoly->color; - ppoly2D->numverts = ppoly->numverts; + zrecip = 1.0 / ppoly->verts[i].v[2]; + ppoly2D->verts[i].x = + ppoly->verts[i].v[0] * zrecip * maxscale + xcenter; + ppoly2D->verts[i].y = DIBHeight - + (ppoly->verts[i].v[1] * zrecip * maxscale + ycenter); } + ppoly2D->color = ppoly->color; + ppoly2D->numverts = ppoly->numverts; +} - // Sort the objects according to z distance from viewpoint. - void ZSortObjects(void) +// Sort the objects according to z distance from viewpoint. +void ZSortObjects(void) +{ + int i, j; + double vdist; + convexobject_t *pobject; + point_t dist; + + objecthead.pnext = &objecthead; + for (i=0 ; ipnext->vdist) - pobject = pobject->pnext; - objects[i].pnext = pobject->pnext; - pobject->pnext = &objects[i]; - } - } - - // Move the view position and set the world->view transform. - void UpdateViewPos() - { - int i; - point_t motionvec; - double s, c, mtemp1[3][3], mtemp2[3][3]; - - // Move in the view direction, across the x-y plane, as if - // walking. This approach moves slower when looking up or - // down at more of an angle - motionvec.v[0] = DotProduct(&vpn, &xaxis); - motionvec.v[1] = 0.0; - motionvec.v[2] = DotProduct(&vpn, &zaxis); - for (i=0 ; i<3 ; i++) - { - currentpos.v[i] += motionvec.v[i] * currentspeed; - if (currentpos.v[i] > MAX_COORD) - currentpos.v[i] = MAX_COORD; - if (currentpos.v[i] < -MAX_COORD) - currentpos.v[i] = -MAX_COORD; - } - // Set up the world-to-view rotation. - // Note: much of the work done in concatenating these matrices - // can be factored out, since it contributes nothing to the - // final result; multiply the three matrices together on paper - // to generate a minimum equation for each of the 9 final elements - s = sin(roll); - c = cos(roll); - mroll[0][0] = c; - mroll[0][1] = s; - mroll[1][0] = -s; - mroll[1][1] = c; - s = sin(pitch); - c = cos(pitch); - mpitch[1][1] = c; - mpitch[1][2] = s; - mpitch[2][1] = -s; - mpitch[2][2] = c; - s = sin(yaw); - c = cos(yaw); - myaw[0][0] = c; - myaw[0][2] = -s; - myaw[2][0] = s; - myaw[2][2] = c; - MConcat(mroll, myaw, mtemp1); - MConcat(mpitch, mtemp1, mtemp2); - // Break out the rotation matrix into vright, vup, and vpn. - // We could work directly with the matrix; breaking it out - // into three vectors is just to make things clearer - for (i=0 ; i<3 ; i++) - { - vright.v[i] = mtemp2[0][i]; - vup.v[i] = mtemp2[1][i]; - vpn.v[i] = mtemp2[2][i]; - } - // Simulate crude friction - if (currentspeed > (MOVEMENT_SPEED * speedscale / 2.0)) - currentspeed -= MOVEMENT_SPEED * speedscale / 2.0; - else if (currentspeed < -(MOVEMENT_SPEED * speedscale / 2.0)) - currentspeed += MOVEMENT_SPEED * speedscale / 2.0; - else - currentspeed = 0.0; - } - - // Rotate a vector from viewspace to worldspace. - void BackRotateVector(point_t *pin, point_t *pout) - { - int i; - - // Rotate into the world orientation - for (i=0 ; i<3 ; i++) - pout->v[i] = pin->v[0] * vright.v[i] + - pin->v[1] * vup.v[i] + - pin->v[2] * vpn.v[i]; - } - - // Transform a point from worldspace to viewspace. - void TransformPoint(point_t *pin, point_t *pout) - { - int i; - point_t tvert; - - // Translate into a viewpoint-relative coordinate - for (i=0 ; i<3 ; i++) - tvert.v[i] = pin->v[i] - currentpos.v[i]; - // Rotate into the view orientation - pout->v[0] = DotProduct(&tvert, &vright); - pout->v[1] = DotProduct(&tvert, &vup); - pout->v[2] = DotProduct(&tvert, &vpn); - } - - // Transform a polygon from worldspace to viewspace. - void TransformPolygon(polygon_t *pinpoly, polygon_t *poutpoly) - { - int i; - - for (i=0 ; inumverts ; i++) - TransformPoint(&pinpoly->verts[i], &poutpoly->verts[i]); - poutpoly->color = pinpoly->color; - poutpoly->numverts = pinpoly->numverts; - } - - // Returns true if polygon faces the viewpoint, assuming a clockwise - // winding of vertices as seen from the front. - int PolyFacesViewer(polygon_t *ppoly) - { - int i; - point_t viewvec, edge1, edge2, normal; - - for (i=0 ; i<3 ; i++) - { - viewvec.v[i] = ppoly->verts[0].v[i] - currentpos.v[i]; - edge1.v[i] = ppoly->verts[0].v[i] - ppoly->verts[1].v[i]; - edge2.v[i] = ppoly->verts[2].v[i] - ppoly->verts[1].v[i]; - } - CrossProduct(&edge1, &edge2, &normal); - if (DotProduct(&viewvec, &normal) > 0) - return 1; - else - return 0; - } - - // Set up a clip plane with the specified normal. - void SetWorldspaceClipPlane(point_t *normal, plane_t *plane) - { - - // Rotate the plane normal into worldspace - BackRotateVector(normal, &plane->normal); - plane->distance = DotProduct(¤tpos, &plane->normal) + - CLIP_PLANE_EPSILON; - } - - // Set up the planes of the frustum, in worldspace coordinates. - void SetUpFrustum(void) - { - double angle, s, c; - point_t normal; - - angle = atan(2.0 / fieldofview * maxscale / xscreenscale); - s = sin(angle); - c = cos(angle); - // Left clip plane - normal.v[0] = s; - normal.v[1] = 0; - normal.v[2] = c; - SetWorldspaceClipPlane(&normal, &frustumplanes[0]); - // Right clip plane - normal.v[0] = -s; - SetWorldspaceClipPlane(&normal, &frustumplanes[1]); - angle = atan(2.0 / fieldofview * maxscale / yscreenscale); - s = sin(angle); - c = cos(angle); - // Bottom clip plane - normal.v[0] = 0; - normal.v[1] = s; - normal.v[2] = c; - SetWorldspaceClipPlane(&normal, &frustumplanes[2]); - // Top clip plane - normal.v[1] = -s; - SetWorldspaceClipPlane(&normal, &frustumplanes[3]); - } - - // Clip a polygon to the frustum. - int ClipToFrustum(polygon_t *pin, polygon_t *pout) - { - int i, curpoly; - polygon_t tpoly[2], *ppoly; - - curpoly = 0; - ppoly = pin; - for (i=0 ; i<(NUM_FRUSTUM_PLANES-1); i++) - { - if (!ClipToPlane(ppoly, - &frustumplanes[i], - &tpoly[curpoly])) - return 0; - ppoly = &tpoly[curpoly]; - curpoly ^= 1; - } - return ClipToPlane(ppoly, - &frustumplanes[NUM_FRUSTUM_PLANES-1], - pout); - } - - // Render the current state of the world to the screen. - void UpdateWorld() - { - HPALETTE holdpal; - HDC hdcScreen, hdcDIBSection; - HBITMAP holdbitmap; - polygon2D_t screenpoly; - polygon_t *ppoly, tpoly0, tpoly1, tpoly2; - convexobject_t *pobject; - int i, j, k; - - UpdateViewPos(); - memset(pDIBBase, 0, DIBWidth*DIBHeight); // clear frame - SetUpFrustum(); - ZSortObjects(); - // Draw all visible faces in all objects - pobject = objecthead.pnext; - while (pobject != &objecthead) - { - ppoly = pobject->ppoly; - for (i=0 ; inumpolys ; i++) - { - // Move the polygon relative to the object center - tpoly0.color = ppoly->color; - tpoly0.numverts = ppoly->numverts; - for (j=0 ; jverts[j].v[k] + - pobject->center.v[k]; - } - if (PolyFacesViewer(&tpoly0)) - { - if (ClipToFrustum(&tpoly0, &tpoly1)) - { - TransformPolygon (&tpoly1, &tpoly2); - ProjectPolygon (&tpoly2, &screenpoly); - FillPolygon2D (&screenpoly); - } - } - ppoly++; - } + for (j=0 ; j<3 ; j++) + dist.v[j] = objects[i].center.v[j] - currentpos.v[j]; + objects[i].vdist = sqrt(dist.v[0] * dist.v[0] + + dist.v[1] * dist.v[1] + + dist.v[2] * dist.v[2]); + pobject = &objecthead; + vdist = objects[i].vdist; + // Viewspace-distance-sort this object into the others. + // Guaranteed to terminate because of sentinel + while (vdist < pobject->pnext->vdist) pobject = pobject->pnext; - } - // We've drawn the frame; copy it to the screen - hdcScreen = GetDC(hwndOutput); - holdpal = SelectPalette(hdcScreen, hpalDIB, FALSE); - RealizePalette(hdcScreen); - hdcDIBSection = CreateCompatibleDC(hdcScreen); - holdbitmap = SelectObject(hdcDIBSection, hDIBSection); - BitBlt(hdcScreen, 0, 0, DIBWidth, DIBHeight, hdcDIBSection, - 0, 0, SRCCOPY); - SelectPalette(hdcScreen, holdpal, FALSE); - ReleaseDC(hwndOutput, hdcScreen); - SelectObject(hdcDIBSection, holdbitmap); - ReleaseDC(hwndOutput, hdcDIBSection); + objects[i].pnext = pobject->pnext; + pobject->pnext = &objects[i]; } +} + +// Move the view position and set the world->view transform. +void UpdateViewPos() +{ + int i; + point_t motionvec; + double s, c, mtemp1[3][3], mtemp2[3][3]; + + // Move in the view direction, across the x-y plane, as if + // walking. This approach moves slower when looking up or + // down at more of an angle + motionvec.v[0] = DotProduct(&vpn, &xaxis); + motionvec.v[1] = 0.0; + motionvec.v[2] = DotProduct(&vpn, &zaxis); + for (i=0 ; i<3 ; i++) + { + currentpos.v[i] += motionvec.v[i] * currentspeed; + if (currentpos.v[i] > MAX_COORD) + currentpos.v[i] = MAX_COORD; + if (currentpos.v[i] < -MAX_COORD) + currentpos.v[i] = -MAX_COORD; + } + // Set up the world-to-view rotation. + // Note: much of the work done in concatenating these matrices + // can be factored out, since it contributes nothing to the + // final result; multiply the three matrices together on paper + // to generate a minimum equation for each of the 9 final elements + s = sin(roll); + c = cos(roll); + mroll[0][0] = c; + mroll[0][1] = s; + mroll[1][0] = -s; + mroll[1][1] = c; + s = sin(pitch); + c = cos(pitch); + mpitch[1][1] = c; + mpitch[1][2] = s; + mpitch[2][1] = -s; + mpitch[2][2] = c; + s = sin(yaw); + c = cos(yaw); + myaw[0][0] = c; + myaw[0][2] = -s; + myaw[2][0] = s; + myaw[2][2] = c; + MConcat(mroll, myaw, mtemp1); + MConcat(mpitch, mtemp1, mtemp2); + // Break out the rotation matrix into vright, vup, and vpn. + // We could work directly with the matrix; breaking it out + // into three vectors is just to make things clearer + for (i=0 ; i<3 ; i++) + { + vright.v[i] = mtemp2[0][i]; + vup.v[i] = mtemp2[1][i]; + vpn.v[i] = mtemp2[2][i]; + } + // Simulate crude friction + if (currentspeed > (MOVEMENT_SPEED * speedscale / 2.0)) + currentspeed -= MOVEMENT_SPEED * speedscale / 2.0; + else if (currentspeed < -(MOVEMENT_SPEED * speedscale / 2.0)) + currentspeed += MOVEMENT_SPEED * speedscale / 2.0; + else + currentspeed = 0.0; +} + +// Rotate a vector from viewspace to worldspace. +void BackRotateVector(point_t *pin, point_t *pout) +{ + int i; + + // Rotate into the world orientation + for (i=0 ; i<3 ; i++) + pout->v[i] = pin->v[0] * vright.v[i] + + pin->v[1] * vup.v[i] + + pin->v[2] * vpn.v[i]; +} + +// Transform a point from worldspace to viewspace. +void TransformPoint(point_t *pin, point_t *pout) +{ + int i; + point_t tvert; + + // Translate into a viewpoint-relative coordinate + for (i=0 ; i<3 ; i++) + tvert.v[i] = pin->v[i] - currentpos.v[i]; + // Rotate into the view orientation + pout->v[0] = DotProduct(&tvert, &vright); + pout->v[1] = DotProduct(&tvert, &vup); + pout->v[2] = DotProduct(&tvert, &vpn); +} + +// Transform a polygon from worldspace to viewspace. +void TransformPolygon(polygon_t *pinpoly, polygon_t *poutpoly) +{ + int i; + + for (i=0 ; inumverts ; i++) + TransformPoint(&pinpoly->verts[i], &poutpoly->verts[i]); + poutpoly->color = pinpoly->color; + poutpoly->numverts = pinpoly->numverts; +} + +// Returns true if polygon faces the viewpoint, assuming a clockwise +// winding of vertices as seen from the front. +int PolyFacesViewer(polygon_t *ppoly) +{ + int i; + point_t viewvec, edge1, edge2, normal; + + for (i=0 ; i<3 ; i++) + { + viewvec.v[i] = ppoly->verts[0].v[i] - currentpos.v[i]; + edge1.v[i] = ppoly->verts[0].v[i] - ppoly->verts[1].v[i]; + edge2.v[i] = ppoly->verts[2].v[i] - ppoly->verts[1].v[i]; + } + CrossProduct(&edge1, &edge2, &normal); + if (DotProduct(&viewvec, &normal) > 0) + return 1; + else + return 0; +} + +// Set up a clip plane with the specified normal. +void SetWorldspaceClipPlane(point_t *normal, plane_t *plane) +{ + + // Rotate the plane normal into worldspace + BackRotateVector(normal, &plane->normal); + plane->distance = DotProduct(¤tpos, &plane->normal) + + CLIP_PLANE_EPSILON; +} + +// Set up the planes of the frustum, in worldspace coordinates. +void SetUpFrustum(void) +{ + double angle, s, c; + point_t normal; + + angle = atan(2.0 / fieldofview * maxscale / xscreenscale); + s = sin(angle); + c = cos(angle); + // Left clip plane + normal.v[0] = s; + normal.v[1] = 0; + normal.v[2] = c; + SetWorldspaceClipPlane(&normal, &frustumplanes[0]); + // Right clip plane + normal.v[0] = -s; + SetWorldspaceClipPlane(&normal, &frustumplanes[1]); + angle = atan(2.0 / fieldofview * maxscale / yscreenscale); + s = sin(angle); + c = cos(angle); + // Bottom clip plane + normal.v[0] = 0; + normal.v[1] = s; + normal.v[2] = c; + SetWorldspaceClipPlane(&normal, &frustumplanes[2]); + // Top clip plane + normal.v[1] = -s; + SetWorldspaceClipPlane(&normal, &frustumplanes[3]); +} + +// Clip a polygon to the frustum. +int ClipToFrustum(polygon_t *pin, polygon_t *pout) +{ + int i, curpoly; + polygon_t tpoly[2], *ppoly; + + curpoly = 0; + ppoly = pin; + for (i=0 ; i<(NUM_FRUSTUM_PLANES-1); i++) + { + if (!ClipToPlane(ppoly, + &frustumplanes[i], + &tpoly[curpoly])) + return 0; + ppoly = &tpoly[curpoly]; + curpoly ^= 1; + } + return ClipToPlane(ppoly, + &frustumplanes[NUM_FRUSTUM_PLANES-1], + pout); +} + +// Render the current state of the world to the screen. +void UpdateWorld() +{ + HPALETTE holdpal; + HDC hdcScreen, hdcDIBSection; + HBITMAP holdbitmap; + polygon2D_t screenpoly; + polygon_t *ppoly, tpoly0, tpoly1, tpoly2; + convexobject_t *pobject; + int i, j, k; + + UpdateViewPos(); + memset(pDIBBase, 0, DIBWidth*DIBHeight); // clear frame + SetUpFrustum(); + ZSortObjects(); + // Draw all visible faces in all objects + pobject = objecthead.pnext; + while (pobject != &objecthead) + { + ppoly = pobject->ppoly; + for (i=0 ; inumpolys ; i++) + { + // Move the polygon relative to the object center + tpoly0.color = ppoly->color; + tpoly0.numverts = ppoly->numverts; + for (j=0 ; jverts[j].v[k] + + pobject->center.v[k]; + } + if (PolyFacesViewer(&tpoly0)) + { + if (ClipToFrustum(&tpoly0, &tpoly1)) + { + TransformPolygon (&tpoly1, &tpoly2); + ProjectPolygon (&tpoly2, &screenpoly); + FillPolygon2D (&screenpoly); + } + } + ppoly++; + } + pobject = pobject->pnext; + } + // We've drawn the frame; copy it to the screen + hdcScreen = GetDC(hwndOutput); + holdpal = SelectPalette(hdcScreen, hpalDIB, FALSE); + RealizePalette(hdcScreen); + hdcDIBSection = CreateCompatibleDC(hdcScreen); + holdbitmap = SelectObject(hdcDIBSection, hDIBSection); + BitBlt(hdcScreen, 0, 0, DIBWidth, DIBHeight, hdcDIBSection, + 0, 0, SRCCOPY); + SelectPalette(hdcScreen, holdpal, FALSE); + ReleaseDC(hwndOutput, hdcScreen); + SelectObject(hdcDIBSection, holdbitmap); + ReleaseDC(hwndOutput, hdcDIBSection); +} +``` diff --git a/67-03.md b/67-03.md index 9edab65..0439805 100644 --- a/67-03.md +++ b/67-03.md @@ -43,435 +43,437 @@ for real-world applications than the 3-D clipping demo from Chapter 65. **Listing 67.1 L67\_1.C** - // Part of Win32 program to demonstrate z-sorted spans. Whitespace - // removed for space reasons. Full source code, with whitespace, - // available from ftp.idsoftware.com/mikeab/ddjzsort.zip. +```c +// Part of Win32 program to demonstrate z-sorted spans. Whitespace +// removed for space reasons. Full source code, with whitespace, +// available from ftp.idsoftware.com/mikeab/ddjzsort.zip. - #define MAX_SPANS 10000 - #define MAX_SURFS 1000 - #define MAX_EDGES 5000 +#define MAX_SPANS 10000 +#define MAX_SURFS 1000 +#define MAX_EDGES 5000 - typedef struct surf_s { - struct surf_s *pnext, *pprev; - int color, visxstart, state; - double zinv00, zinvstepx, zinvstepy; - } surf_t; +typedef struct surf_s { + struct surf_s *pnext, *pprev; + int color, visxstart, state; + double zinv00, zinvstepx, zinvstepy; +} surf_t; - typedef struct edge_s { - int x, xstep, leading; - surf_t *psurf; - struct edge_s *pnext, *pprev, *pnextremove; - } edge_t; +typedef struct edge_s { + int x, xstep, leading; + surf_t *psurf; + struct edge_s *pnext, *pprev, *pnextremove; +} edge_t; - // Span, edge, and surface lists - span_t spans[MAX_SPANS]; - edge_t edges[MAX_EDGES]; - surf_t surfs[MAX_SURFS]; +// Span, edge, and surface lists +span_t spans[MAX_SPANS]; +edge_t edges[MAX_EDGES]; +surf_t surfs[MAX_SURFS]; - // Bucket list of new edges to add on each scan line - edge_t newedges[MAX_SCREEN_HEIGHT]; +// Bucket list of new edges to add on each scan line +edge_t newedges[MAX_SCREEN_HEIGHT]; - // Bucket list of edges to remove on each scan line - edge_t *removeedges[MAX_SCREEN_HEIGHT]; +// Bucket list of edges to remove on each scan line +edge_t *removeedges[MAX_SCREEN_HEIGHT]; - // Head and tail for the active edge list - edge_t edgehead, edgetail; +// Head and tail for the active edge list +edge_t edgehead, edgetail; - // Edge used as sentinel of new edge lists - edge_t maxedge = {0x7FFFFFFF}; +// Edge used as sentinel of new edge lists +edge_t maxedge = {0x7FFFFFFF}; - // Head/tail/sentinel/background surface of active surface stack - surf_t surfstack; +// Head/tail/sentinel/background surface of active surface stack +surf_t surfstack; - // pointers to next available surface and edge - surf_t *pavailsurf; - edge_t *pavailedge; +// pointers to next available surface and edge +surf_t *pavailsurf; +edge_t *pavailedge; - // Returns true if polygon faces the viewpoint, assuming a clockwise - // winding of vertices as seen from the front. - int PolyFacesViewer(polygon_t *ppoly, plane_t *pplane) - { - int i; - point_t viewvec; +// Returns true if polygon faces the viewpoint, assuming a clockwise +// winding of vertices as seen from the front. +int PolyFacesViewer(polygon_t *ppoly, plane_t *pplane) +{ + int i; + point_t viewvec; - 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 - // sharply that the gradients are unusable or invalid - if (DotProduct (&viewvec, &pplane->normal) < -0.01) - return 1; - return 0; + 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 + // sharply that the gradients are unusable or invalid + if (DotProduct (&viewvec, &pplane->normal) < -0.01) + return 1; + return 0; +} + + +// Add the polygon's edges to the global edge table. +void AddPolygonEdges (plane_t *plane, polygon2D_t *screenpoly) +{ + double distinv, deltax, deltay, slope; + int i, nextvert, numverts, temp, topy, bottomy, height; + edge_t *pedge; + + numverts = screenpoly->numverts; + + // 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[i].x < -0.5) + screenpoly->verts[i].x = -0.5; + if (screenpoly->verts[i].x > ((double)DIBWidth - 0.5)) + screenpoly->verts[i].x = (double)DIBWidth - 0.5; + if (screenpoly->verts[i].y < -0.5) + screenpoly->verts[i].y = -0.5; + if (screenpoly->verts[i].y > ((double)DIBHeight - 0.5)) + screenpoly->verts[i].y = (double)DIBHeight - 0.5; } - - // Add the polygon's edges to the global edge table. - void AddPolygonEdges (plane_t *plane, polygon2D_t *screenpoly) - { - double distinv, deltax, deltay, slope; - int i, nextvert, numverts, temp, topy, bottomy, height; - edge_t *pedge; - - numverts = screenpoly->numverts; - - // 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[i].x < -0.5) - screenpoly->verts[i].x = -0.5; - if (screenpoly->verts[i].x > ((double)DIBWidth - 0.5)) - screenpoly->verts[i].x = (double)DIBWidth - 0.5; - if (screenpoly->verts[i].y < -0.5) - screenpoly->verts[i].y = -0.5; - if (screenpoly->verts[i].y > ((double)DIBHeight - 0.5)) - screenpoly->verts[i].y = (double)DIBHeight - 0.5; + // Add each edge in turn + for (i=0 ; i= numverts) + nextvert = 0; + topy = (int)ceil(screenpoly->verts[i].y); + bottomy = (int)ceil(screenpoly->verts[nextvert].y); + height = bottomy - topy; + if (height == 0) + continue; // doesn't cross any scan lines + if (height < 0) { + // Leading edge + temp = topy; + topy = bottomy; + bottomy = temp; + pavailedge->leading = 1; + deltax = screenpoly->verts[i].x - + screenpoly->verts[nextvert].x; + deltay = screenpoly->verts[i].y - + screenpoly->verts[nextvert].y; + slope = deltax / deltay; + // Edge coordinates are in 16.16 fixed point + pavailedge->xstep = (int)(slope * (float)0x10000); + pavailedge->x = (int)((screenpoly->verts[nextvert].x + + ((float)topy - screenpoly->verts[nextvert].y) * + slope) * (float)0x10000); + } else { + // Trailing edge + pavailedge->leading = 0; + deltax = screenpoly->verts[nextvert].x - + screenpoly->verts[i].x; + deltay = screenpoly->verts[nextvert].y - + screenpoly->verts[i].y; + slope = deltax / deltay; + // Edge coordinates are in 16.16 fixed point + pavailedge->xstep = (int)(slope * (float)0x10000); + pavailedge->x = (int)((screenpoly->verts[i].x + + ((float)topy - screenpoly->verts[i].y) * slope) * + (float)0x10000); } - // Add each edge in turn - for (i=0 ; i= numverts) - nextvert = 0; - topy = (int)ceil(screenpoly->verts[i].y); - bottomy = (int)ceil(screenpoly->verts[nextvert].y); - height = bottomy - topy; - if (height == 0) - continue; // doesn't cross any scan lines - if (height < 0) { - // Leading edge - temp = topy; - topy = bottomy; - bottomy = temp; - pavailedge->leading = 1; - deltax = screenpoly->verts[i].x - - screenpoly->verts[nextvert].x; - deltay = screenpoly->verts[i].y - - screenpoly->verts[nextvert].y; - slope = deltax / deltay; - // Edge coordinates are in 16.16 fixed point - pavailedge->xstep = (int)(slope * (float)0x10000); - pavailedge->x = (int)((screenpoly->verts[nextvert].x + - ((float)topy - screenpoly->verts[nextvert].y) * - slope) * (float)0x10000); + // Put the edge on the list to be added on top scan + pedge = &newedges[topy]; + while (pedge->pnext->x < pavailedge->x) + pedge = pedge->pnext; + pavailedge->pnext = pedge->pnext; + pedge->pnext = pavailedge; + + // Put the edge on the list to be removed after final scan + pavailedge->pnextremove = removeedges[bottomy - 1]; + removeedges[bottomy - 1] = pavailedge; + + // Associate the edge with the surface we'll create for + // this polygon + pavailedge->psurf = pavailsurf; + + // 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 + // the edges + pavailsurf->state = 0; + pavailsurf->color = currentcolor; + + // Set up the 1/z gradients from the polygon, calculating the + // base value at screen coordinate 0,0 so we can use screen + // coordinates directly when calculating 1/z from the gradients + distinv = 1.0 / plane->distance; + pavailsurf->zinvstepx = plane->normal.v[0] * distinv * + maxscreenscaleinv * (fieldofview / 2.0); + pavailsurf->zinvstepy = -plane->normal.v[1] * distinv * + maxscreenscaleinv * (fieldofview / 2.0); + pavailsurf->zinv00 = plane->normal.v[2] * distinv - + xcenter * pavailsurf->zinvstepx - + ycenter * pavailsurf->zinvstepy; + + // Make sure we don't overflow the surface array + if (pavailsurf < &surfs[MAX_SURFS]) + pavailsurf++; +} + + +// Scan all the edges in the global edge table into spans. +void ScanEdges (void) +{ + int x, y; + double fx, fy, zinv, zinv2; + edge_t *pedge, *pedge2, *ptemp; + span_t *pspan; + surf_t *psurf, *psurf2; + + pspan = spans; + + // Set up the active edge list as initially empty, containing + // only the sentinels (which are also the background fill). Most + // of these fields could be set up just once at start-up + edgehead.pnext = &edgetail; + edgehead.pprev = NULL; + edgehead.x = -0xFFFF; // left edge of screen + edgehead.leading = 1; + edgehead.psurf = &surfstack; + edgetail.pnext = NULL; // mark edge of list + edgetail.pprev = &edgehead; + edgetail.x = DIBWidth << 16; // right edge of screen + edgetail.leading = 0; + edgetail.psurf = &surfstack; + + // The background surface is the entire stack initially, and + // is infinitely far away, so everything sorts in front of it. + // This could be set just once at start-up + surfstack.pnext = surfstack.pprev = &surfstack; + surfstack.color = 0; + surfstack.zinv00 = -999999.0; + surfstack.zinvstepx = surfstack.zinvstepy = 0.0; + for (y=0 ; yx > pedge2->pnext->x) + pedge2 = pedge2->pnext; + ptemp = pedge->pnext; + pedge->pnext = pedge2->pnext; + pedge->pprev = pedge2; + pedge2->pnext->pprev = pedge; + pedge2->pnext = pedge; + pedge2 = pedge; + pedge = ptemp; + } + + // Scan out the active edges into spans + // Start out with the left background edge already inserted, + // and the surface stack containing only the background + surfstack.state = 1; + surfstack.visxstart = 0; + for (pedge=edgehead.pnext ; pedge ; pedge=pedge->pnext) { + psurf = pedge->psurf; + if (pedge->leading) { + // 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 + // for the current top. + // 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 + zinv = psurf->zinv00 + psurf->zinvstepx * fx + + psurf->zinvstepy * fy; + // See if that makes it a new top surface + psurf2 = surfstack.pnext; + zinv2 = psurf2->zinv00 + psurf2->zinvstepx * fx + + psurf2->zinvstepy * fy; + if (zinv >= zinv2) { + // It's a new top surface + // emit the span for the current top + x = (pedge->x + 0xFFFF) >> 16; + pspan->count = x - psurf2->visxstart; + if (pspan->count > 0) { + pspan->y = y; + pspan->x = psurf2->visxstart; + pspan->color = psurf2->color; + // Make sure we don't overflow + // the span array + if (pspan < &spans[MAX_SPANS]) + pspan++; + } + psurf->visxstart = x; + // Add the edge to the stack + psurf->pnext = psurf2; + psurf2->pprev = psurf; + surfstack.pnext = psurf; + psurf->pprev = &surfstack; + } else { + // Not a new top; sort into the surface stack. + // Guaranteed to terminate due to sentinel + // background surface + do { + psurf2 = psurf2->pnext; + zinv2 = psurf2->zinv00 + + psurf2->zinvstepx * fx + + psurf2->zinvstepy * fy; + } while (zinv < zinv2); + // Insert the surface into the stack + psurf->pnext = psurf2; + psurf->pprev = psurf2->pprev; + psurf2->pprev->pnext = psurf; + psurf2->pprev = psurf; + } + } } else { - // Trailing edge - pavailedge->leading = 0; - deltax = screenpoly->verts[nextvert].x - - screenpoly->verts[i].x; - deltay = screenpoly->verts[nextvert].y - - screenpoly->verts[i].y; - slope = deltax / deltay; - // Edge coordinates are in 16.16 fixed point - pavailedge->xstep = (int)(slope * (float)0x10000); - pavailedge->x = (int)((screenpoly->verts[i].x + - ((float)topy - screenpoly->verts[i].y) * slope) * - (float)0x10000); - } - - // Put the edge on the list to be added on top scan - pedge = &newedges[topy]; - while (pedge->pnext->x < pavailedge->x) - pedge = pedge->pnext; - pavailedge->pnext = pedge->pnext; - pedge->pnext = pavailedge; - - // Put the edge on the list to be removed after final scan - pavailedge->pnextremove = removeedges[bottomy - 1]; - removeedges[bottomy - 1] = pavailedge; - - // Associate the edge with the surface we'll create for - // this polygon - pavailedge->psurf = pavailsurf; - - // 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 - // the edges - pavailsurf->state = 0; - pavailsurf->color = currentcolor; - - // Set up the 1/z gradients from the polygon, calculating the - // base value at screen coordinate 0,0 so we can use screen - // coordinates directly when calculating 1/z from the gradients - distinv = 1.0 / plane->distance; - pavailsurf->zinvstepx = plane->normal.v[0] * distinv * - maxscreenscaleinv * (fieldofview / 2.0); - pavailsurf->zinvstepy = -plane->normal.v[1] * distinv * - maxscreenscaleinv * (fieldofview / 2.0); - pavailsurf->zinv00 = plane->normal.v[2] * distinv - - xcenter * pavailsurf->zinvstepx - - ycenter * pavailsurf->zinvstepy; - - // Make sure we don't overflow the surface array - if (pavailsurf < &surfs[MAX_SURFS]) - pavailsurf++; - } - - - // Scan all the edges in the global edge table into spans. - void ScanEdges (void) - { - int x, y; - double fx, fy, zinv, zinv2; - edge_t *pedge, *pedge2, *ptemp; - span_t *pspan; - surf_t *psurf, *psurf2; - - pspan = spans; - - // Set up the active edge list as initially empty, containing - // only the sentinels (which are also the background fill). Most - // of these fields could be set up just once at start-up - edgehead.pnext = &edgetail; - edgehead.pprev = NULL; - edgehead.x = -0xFFFF; // left edge of screen - edgehead.leading = 1; - edgehead.psurf = &surfstack; - edgetail.pnext = NULL; // mark edge of list - edgetail.pprev = &edgehead; - edgetail.x = DIBWidth << 16; // right edge of screen - edgetail.leading = 0; - edgetail.psurf = &surfstack; - - // The background surface is the entire stack initially, and - // is infinitely far away, so everything sorts in front of it. - // This could be set just once at start-up - surfstack.pnext = surfstack.pprev = &surfstack; - surfstack.color = 0; - surfstack.zinv00 = -999999.0; - surfstack.zinvstepx = surfstack.zinvstepy = 0.0; - for (y=0 ; yx > pedge2->pnext->x) - pedge2 = pedge2->pnext; - ptemp = pedge->pnext; - pedge->pnext = pedge2->pnext; - pedge->pprev = pedge2; - pedge2->pnext->pprev = pedge; - pedge2->pnext = pedge; - pedge2 = pedge; - pedge = ptemp; - } - - // Scan out the active edges into spans - // Start out with the left background edge already inserted, - // and the surface stack containing only the background - surfstack.state = 1; - surfstack.visxstart = 0; - for (pedge=edgehead.pnext ; pedge ; pedge=pedge->pnext) { - psurf = pedge->psurf; - if (pedge->leading) { - // 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 - // for the current top. - // 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 - zinv = psurf->zinv00 + psurf->zinvstepx * fx + - psurf->zinvstepy * fy; - // See if that makes it a new top surface - psurf2 = surfstack.pnext; - zinv2 = psurf2->zinv00 + psurf2->zinvstepx * fx + - psurf2->zinvstepy * fy; - if (zinv >= zinv2) { - // It's a new top surface - // emit the span for the current top - x = (pedge->x + 0xFFFF) >> 16; - pspan->count = x - psurf2->visxstart; - if (pspan->count > 0) { - pspan->y = y; - pspan->x = psurf2->visxstart; - pspan->color = psurf2->color; - // Make sure we don't overflow - // the span array - if (pspan < &spans[MAX_SPANS]) - pspan++; - } - psurf->visxstart = x; - // Add the edge to the stack - psurf->pnext = psurf2; - psurf2->pprev = psurf; - surfstack.pnext = psurf; - psurf->pprev = &surfstack; - } else { - // Not a new top; sort into the surface stack. - // Guaranteed to terminate due to sentinel - // background surface - do { - psurf2 = psurf2->pnext; - zinv2 = psurf2->zinv00 + - psurf2->zinvstepx * fx + - psurf2->zinvstepy * fy; - } while (zinv < zinv2); - // Insert the surface into the stack - psurf->pnext = psurf2; - psurf->pprev = psurf2->pprev; - psurf2->pprev->pnext = psurf; - psurf2->pprev = psurf; + // 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 + if (—psurf->state == 0) { + if (surfstack.pnext == psurf) { + // 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 + // the span array + if (pspan < &spans[MAX_SPANS]) + pspan++; } + psurf->pnext->visxstart = x; } - } else { - // 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 - if (—psurf->state == 0) { - if (surfstack.pnext == psurf) { - // 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 - // the span array - if (pspan < &spans[MAX_SPANS]) - pspan++; - } - psurf->pnext->visxstart = x; - } - // Remove the surface from the stack - psurf->pnext->pprev = psurf->pprev; - psurf->pprev->pnext = psurf->pnext; - } + // Remove the surface from the stack + psurf->pnext->pprev = psurf->pprev; + psurf->pprev->pnext = psurf->pnext; } } - - // Remove edges that are done - pedge = removeedges[y]; - while (pedge) { - pedge->pprev->pnext = pedge->pnext; - pedge->pnext->pprev = pedge->pprev; - pedge = pedge->pnextremove; - } - - // Step the remaining edges one scan line, and re-sort - for (pedge=edgehead.pnext ; pedge != &edgetail ; ) { - ptemp = pedge->pnext; - // Step the edge - pedge->x += pedge->xstep; - // Move the edge back to the proper sorted location, - // if necessary - while (pedge->x < pedge->pprev->x) { - pedge2 = pedge->pprev; - pedge2->pnext = pedge->pnext; - pedge->pnext->pprev = pedge2; - pedge2->pprev->pnext = pedge; - pedge->pprev = pedge2->pprev; - pedge->pnext = pedge2; - pedge2->pprev = pedge; - } - pedge = ptemp; - } } - pspan->x = -1; // mark the end of the list - } - // Draw all the spans that were scanned out. - void DrawSpans (void) - { - span_t *pspan; - for (pspan=spans ; pspan->x != -1 ; pspan++) - memset (pDIB + (DIBPitch * pspan->y) + pspan->x, - pspan->color, - pspan->count); - } + // Remove edges that are done + pedge = removeedges[y]; + while (pedge) { + pedge->pprev->pnext = pedge->pnext; + pedge->pnext->pprev = pedge->pprev; + pedge = pedge->pnextremove; + } - - // Clear the lists of edges to add and remove on each scan line. - void ClearEdgeLists(void) - { - int i; - for (i=0 ; ipnext; + // Step the edge + pedge->x += pedge->xstep; + // Move the edge back to the proper sorted location, + // if necessary + while (pedge->x < pedge->pprev->x) { + pedge2 = pedge->pprev; + pedge2->pnext = pedge->pnext; + pedge->pnext->pprev = pedge2; + pedge2->pprev->pnext = pedge; + pedge->pprev = pedge2->pprev; + pedge->pnext = pedge2; + pedge2->pprev = pedge; + } + pedge = ptemp; } } + pspan->x = -1; // mark the end of the list +} - // Render the current state of the world to the screen. - void UpdateWorld() - { - HPALETTE holdpal; - HDC hdcScreen, hdcDIBSection; - HBITMAP holdbitmap; - polygon2D_t screenpoly; - polygon_t *ppoly, tpoly0, tpoly1, tpoly2; - convexobject_t *pobject; - int i, j, k; - plane_t plane; - point_t tnormal; +// Draw all the spans that were scanned out. +void DrawSpans (void) +{ + span_t *pspan; + for (pspan=spans ; pspan->x != -1 ; pspan++) + memset (pDIB + (DIBPitch * pspan->y) + pspan->x, + pspan->color, + pspan->count); +} - UpdateViewPos(); - SetUpFrustum(); - ClearEdgeLists(); - pavailsurf = surfs; - pavailedge = edges; - // Draw all visible faces in all objects - pobject = objecthead.pnext; - while (pobject != &objecthead) { - ppoly = pobject->ppoly; - for (i=0 ; inumpolys ; i++) { - // Move the polygon relative to the object center - tpoly0.numverts = ppoly[i].numverts; - for (j=0 ; jcenter.v[k]; - } - if (PolyFacesViewer(&tpoly0, &ppoly[i].plane)) { - if (ClipToFrustum(&tpoly0, &tpoly1)) { - currentcolor = ppoly[i].color; - TransformPolygon (&tpoly1, &tpoly2); - ProjectPolygon (&tpoly2, &screenpoly); +// Clear the lists of edges to add and remove on each scan line. +void ClearEdgeLists(void) +{ + int i; + for (i=0 ; icenter, &tnormal); +// Render the current state of the world to the screen. +void UpdateWorld() +{ + HPALETTE holdpal; + HDC hdcScreen, hdcDIBSection; + HBITMAP holdbitmap; + polygon2D_t screenpoly; + polygon_t *ppoly, tpoly0, tpoly1, tpoly2; + convexobject_t *pobject; + int i, j, k; + plane_t plane; + point_t tnormal; - // Now transform it into viewspace - // Determine the distance from the viewpont - plane.distance -= - DotProduct (¤tpos, &tnormal); + UpdateViewPos(); + SetUpFrustum(); + ClearEdgeLists(); + pavailsurf = surfs; + pavailedge = edges; - // Rotate the normal into view orientation - plane.normal.v[0] = - DotProduct (&tnormal, &vright); - plane.normal.v[1] = - DotProduct (&tnormal, &vup); - plane.normal.v[2] = - DotProduct (&tnormal, &vpn); - AddPolygonEdges (&plane, &screenpoly); - } + // Draw all visible faces in all objects + pobject = objecthead.pnext; + while (pobject != &objecthead) { + ppoly = pobject->ppoly; + for (i=0 ; inumpolys ; i++) { + // Move the polygon relative to the object center + tpoly0.numverts = ppoly[i].numverts; + for (j=0 ; jcenter.v[k]; + } + if (PolyFacesViewer(&tpoly0, &ppoly[i].plane)) { + if (ClipToFrustum(&tpoly0, &tpoly1)) { + currentcolor = ppoly[i].color; + TransformPolygon (&tpoly1, &tpoly2); + ProjectPolygon (&tpoly2, &screenpoly); + + // 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 + + DotProduct (&pobject->center, &tnormal); + + // Now transform it into viewspace + // Determine the distance from the viewpont + plane.distance -= + DotProduct (¤tpos, &tnormal); + + // Rotate the normal into view orientation + plane.normal.v[0] = + DotProduct (&tnormal, &vright); + plane.normal.v[1] = + DotProduct (&tnormal, &vup); + plane.normal.v[2] = + DotProduct (&tnormal, &vpn); + AddPolygonEdges (&plane, &screenpoly); } } - pobject = pobject->pnext; } - ScanEdges (); - DrawSpans (); - - // We've drawn the frame; copy it to the screen - hdcScreen = GetDC(hwndOutput); - holdpal = SelectPalette(hdcScreen, hpalDIB, FALSE); - RealizePalette(hdcScreen); - hdcDIBSection = CreateCompatibleDC(hdcScreen); - holdbitmap = SelectObject(hdcDIBSection, hDIBSection); - BitBlt(hdcScreen, 0, 0, DIBWidth, DIBHeight, hdcDIBSection, - 0, 0, SRCCOPY); - SelectPalette(hdcScreen, holdpal, FALSE); - ReleaseDC(hwndOutput, hdcScreen); - SelectObject(hdcDIBSection, holdbitmap); - DeleteDC(hdcDIBSection); + pobject = pobject->pnext; } + ScanEdges (); + DrawSpans (); + + // We've drawn the frame; copy it to the screen + hdcScreen = GetDC(hwndOutput); + holdpal = SelectPalette(hdcScreen, hpalDIB, FALSE); + RealizePalette(hdcScreen); + hdcDIBSection = CreateCompatibleDC(hdcScreen); + holdbitmap = SelectObject(hdcDIBSection, hDIBSection); + BitBlt(hdcScreen, 0, 0, DIBWidth, DIBHeight, hdcDIBSection, + 0, 0, SRCCOPY); + SelectPalette(hdcScreen, holdpal, FALSE); + ReleaseDC(hwndOutput, hdcScreen); + SelectObject(hdcDIBSection, holdbitmap); + DeleteDC(hdcDIBSection); +} +``` diff --git a/69-04.md b/69-04.md index f8f524e..70ecc75 100644 --- a/69-04.md +++ b/69-04.md @@ -56,118 +56,120 @@ clear success. **LISTING 69.1 L69-1.C** - // 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 - // new vertex. Results are less accurate than from a precise - // affine or perspective texture mapper, and drawing boundaries - // are not identical to those of a precise polygon drawer, although - // they are consistent between adjacent polygons drawn with this - // technique. - // - // Invented and implemented by John Carmack of id Software. +```c +// 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 +// new vertex. Results are less accurate than from a precise +// affine or perspective texture mapper, and drawing boundaries +// are not identical to those of a precise polygon drawer, although +// they are consistent between adjacent polygons drawn with this +// technique. +// +// Invented and implemented by John Carmack of id Software. - void D_PolysetRecursiveTriangle (int *lp1, int *lp2, int *lp3) - { - int *temp; - int d; +void D_PolysetRecursiveTriangle (int *lp1, int *lp2, int *lp3) +{ +int *temp; +int d; - int new[6]; - int z; - short *zbuf; +int new[6]; +int z; +short *zbuf; - // 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; - d = lp2[1] - lp1[1]; - if (d < -1 || d > 1) - goto split; - d = lp3[0] - lp2[0]; - if (d < -1 || d > 1) - goto split2; - d = lp3[1] - lp2[1]; - if (d < -1 || d > 1) - goto split2; - d = lp1[0] - lp3[0]; - if (d < -1 || d > 1) - goto split3; - d = lp1[1] - lp3[1]; - if (d < -1 || d > 1) - { - split3: - // shuffle points so first edge is edge to split - temp = lp1; - lp1 = lp3; - lp3 = lp2; - lp2 = temp; - goto split; - } +// 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; +d = lp2[1] - lp1[1]; +if (d < -1 || d > 1) +goto split; +d = lp3[0] - lp2[0]; +if (d < -1 || d > 1) +goto split2; +d = lp3[1] - lp2[1]; +if (d < -1 || d > 1) +goto split2; +d = lp1[0] - lp3[0]; +if (d < -1 || d > 1) +goto split3; +d = lp1[1] - lp3[1]; +if (d < -1 || d > 1) +{ +split3: +// shuffle points so first edge is edge to split +temp = lp1; +lp1 = lp3; +lp3 = lp2; +lp2 = temp; +goto split; +} - return; // no pixels left to fill in triangle +return; // no pixels left to fill in triangle - split2: - // shuffle points so first edge is edge to split - temp = lp1; - lp1 = lp2; - lp2 = lp3; - lp3 = temp; +split2: +// shuffle points so first edge is edge to split +temp = lp1; +lp1 = lp2; +lp2 = lp3; +lp3 = temp; - split: - // split first edge screen x, screen y, texture s, texture t, and z - // to form a new vertex. Lighting (index 4) is ignored; the - // difference between interpolating lighting and using the same - // shading for the entire triangle is unnoticeable for small - // triangles, so we just use the lighting for the first vertex of - // the original triangle (which was used during set-up to set - // d_colormap, used below to look up lit texels) - new[0] = (lp1[0] + lp2[0]) >> 1; // split screen x - new[1] = (lp1[1] + lp2[1]) >> 1; // split screen y - new[2] = (lp1[2] + lp2[2]) >> 1; // split texture s - new[3] = (lp1[3] + lp2[3]) >> 1; // split texture t - new[5] = (lp1[5] + lp2[5]) >> 1; // split z +split: +// split first edge screen x, screen y, texture s, texture t, and z +// to form a new vertex. Lighting (index 4) is ignored; the +// difference between interpolating lighting and using the same +// shading for the entire triangle is unnoticeable for small +// triangles, so we just use the lighting for the first vertex of +// the original triangle (which was used during set-up to set +// d_colormap, used below to look up lit texels) +new[0] = (lp1[0] + lp2[0]) >> 1; // split screen x +new[1] = (lp1[1] + lp2[1]) >> 1; // split screen y +new[2] = (lp1[2] + lp2[2]) >> 1; // split texture s +new[3] = (lp1[3] + lp2[3]) >> 1; // split texture t +new[5] = (lp1[5] + lp2[5]) >> 1; // split z - // draw the point if splitting a leading edge - if (lp2[1] > lp1[1]) - goto nodraw; - if ((lp2[1] == lp1[1]) && (lp2[0] < lp1[0])) - goto nodraw; +// draw the point if splitting a leading edge +if (lp2[1] > lp1[1]) +goto nodraw; +if ((lp2[1] == lp1[1]) && (lp2[0] < lp1[0])) +goto nodraw; - z = new[5]>>16; +z = new[5]>>16; - // 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]; +// 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 - // indicated by the z-buffer - if (z >= *zbuf) - { - int pix; +// 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 - *zbuf = z; +// set the z-buffer to the new pixel's distance +*zbuf = z; - // 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 - // t are in 16.16 format - pix = d_pcolormap[skintable[new[3]>>16][new[2]>>16]]; +// 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 +// t are in 16.16 format +pix = d_pcolormap[skintable[new[3]>>16][new[2]>>16]]; - // draw the pixel, looking up the scanline start address - // based on screen y and adding in the screen x coordinate - d_viewbuffer[d_scantable[new[1]] + new[0]] = pix; - } +// draw the pixel, looking up the scanline start address +// based on screen y and adding in the screen x coordinate +d_viewbuffer[d_scantable[new[1]] + new[0]] = pix; +} - nodraw: - // recursively draw the two new triangles we created by adding the - // split vertex - D_PolysetRecursiveTriangle (lp3, lp1, new); - D_PolysetRecursiveTriangle (lp3, new, lp2); - } +nodraw: +// recursively draw the two new triangles we created by adding the +// split vertex +D_PolysetRecursiveTriangle (lp3, lp1, new); +D_PolysetRecursiveTriangle (lp3, new, lp2); +} +``` ![**Figure 69.2**  *One recursive subdivision triangle-drawing step.*](images/69-02.jpg)