|
|
![]() ![]() ![]() ![]() ![]() ![]() |
![]() |
|
|
|
Why didnt I catch this bug? Neither I nor a single one of my testers had a fixed-frequency monitor! This nicely illustrates how difficult it is these days to test code in all the PC-compatible environments in which it might run. The problem is particularly severe for small developers, who cant afford to buy every model of every hardware component from every manufacturer; just imagine trying to test network-aware software in all possible configurations! When people ask why software isnt bulletproof; why it crashes or doesnt coexist with certain programs; why PC clones arent always compatible; why, in short, the myriad irritations of using a PC existthis is a big part of the reason. I guess thats just the price we pay for the unfettered creativity and vast choice of the PC market. After setting up mode 13H, Listing 32.1 alters the vertical counts and timings to select 480 visible scan lines. (Theres no need to alter any horizontal values, because mode 13H and Mode X both have 320-pixel horizontal resolutions.) The Maximum Scan Line register is programmed to double scan each line (that is, repeat each scan line twice), however, so we get an effective vertical resolution of 240 scan lines. It is, in fact, possible to get 400 or 480 independent scan lines in 256-color mode, as discussed in Chapter 9 and 10; however, 400-scan-line modes lack square pixels and cant support simultaneous offscreen memory and page flipping. Furthermore, 480-scan-line modes lack page flipping altogether, due to memory constraints. At the same time, Listing 32.1 programs the VGAs bitmap to a planar organization that is similar to that used by the 16-color modes, and utterly different from the linear bitmap of mode 13H. The bizarre bitmap organization of Mode X is shown in Figure 32.1. The first pixel (the pixel at the upper left corner of the screen) is controlled by the byte at offset 0 in plane 0. (The one thing that Mode X blessedly has in common with mode 13H is that each pixel is controlled by a single byte, eliminating the need to mask out individual bits of display memory.) The second pixel, immediately to the right of the first pixel, is controlled by the byte at offset 0 in plane 1. The third pixel comes from offset 0 in plane 2, and the fourth pixel from offset 0 in plane 3. Then, the fifth pixel is controlled by the byte at offset 1 in plane 0, and that cycle continues, with each group of four pixels spread across the four planes at the same address. The offset M of pixel N in display memory is M = N/4, and the plane P of pixel N is P = N mod 4. For display memory writes, the plane is selected by setting bit P of the Map Mask register (Sequence Controller register 2) to 1 and all other bits to 0; for display memory reads, the plane is selected by setting the Read Map register (Graphics Controller register 4) to P.
It goes without saying that this is one ugly bitmap organization, requiring a lot of overhead to manipulate a single pixel. The write pixel code shown in Listing 32.2 must determine the appropriate plane and perform a 16-bit OUT to select that plane for each pixel written, and likewise for the read pixel code shown in Listing 32.3. Calculating and mapping in a plane once for each pixel written is scarcely a recipe for performance. LISTING 32.2 L32-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);
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
.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 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
pop bp ;restore caller's stack frame
ret
_WritePixelX endp
end
LISTING 32.3 L32-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);
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
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 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
Thats all right, though, because most graphics software spends little time drawing individual pixels. Ive provided the write and read pixel routines as basic primitives, and so youll understand how the bitmap is organized, but the building blocks of high-performance graphics software are fills, copies, and bitblts, and its there that Mode X shines. Designing from a Mode X PerspectiveListing 32.4 shows Mode X rectangle fill code. The plane is selected for each pixel in turn, with drawing cycling from plane 0 to plane 3, then wrapping back to plane 0. This is the sort of code that stems from a write-pixel line of thinking; it reflects not a whit of the unique perspective that Mode X demands, and although it looks reasonably efficient, it is in fact some of the slowest graphics code you will ever see. Ive provided Listing 32.4 partly for illustrative purposes, but mostly so well have a point of reference for the substantial speed-up thats possible with code thats designed from a Mode X perspective. LISTING 32.4 L32-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);
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
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 32.4 both result from selecting the plane on a pixel by pixel basis. First, endless OUTs (which are particularly slow on 386s, 486s, and Pentiums, much slower than accesses to display memory) must be performed, and, second, REP STOS cant be used. Listing 32.5 overcomes both these problems by tailoring the fill technique to the organization of display memory. Each plane is filled in its entirety in one burst before the next plane is processed, so only five OUTs are required in all, and REP STOS can indeed be used; Ive used REP STOSB in Listings 32.5 and 32.6. REP STOSW could be used and would improve performance on most VGAs; however, REP STOSW requires extra overhead to set up, so it can be slower for small rectangles, especially on 8-bit VGAs. Note that doing an entire plane at a time can produce a fading-in effect for large images, because all columns for one plane are drawn before any columns for the next. If this is a problem, the four planes can be cycled through once for each scan line, rather than once for the entire rectangle. LISTING 32.5 L32-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);
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
.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
|
![]() |
![]() ![]() ![]() ![]() ![]() ![]() |
|