abrash-black-book/40-04.md
2013-12-30 20:26:41 +11:00

114 lines
5.8 KiB
Markdown

Listing 40.4 illustrates several interesting aspects of polygon filling.
The first and third polygons drawn illustrate the operation of the
odd/even fill rule. The second polygon drawn illustrates how holes can
be created in seemingly solid objects; an edge runs from the outside of
the rectangle to the inside, the edges comprising the hole are defined,
and then the same edge is used to move back to the outside; because the
edges join seamlessly, the rectangle appears to form a solid boundary
around the hole.
The set of V-shaped polygons drawn by Listing 40.4 demonstrate that
polygons sharing common edges meet but do not overlap. This
characteristic, which I discussed at length in Chapter 38, is not a
trivial matter; it allows polygons to fit together without fear of
overlapping or missed pixels. In general, Listing 40.1 guarantees that
polygons are filled such that common boundaries and vertices are drawn
once and only once. This has the side-effect for any individual polygon
of not drawing pixels that lie exactly on the bottom or right boundaries
or at vertices that terminate bottom or right boundaries.
By the way, I have not seen polygon boundary filling handled precisely
this way elsewhere. The boundary filling approach in Foley and van Dam
is similar, but seems to me to not draw all boundary and vertex pixels
once and only once.
#### More on Active Edges {#Heading6}
Edges of zero height—horizontal edges and edges defined by two vertices
at the same location—never even make it into the GET in Listing 40.1. A
polygon edge of zero height can never be an active edge, because it can
never intersect a scan line; it can only run along the scan line, and
the span it runs along is defined not by that edge but by the edges that
connect to its endpoints.
#### Performance Considerations {#Heading7}
How fast is Listing 40.1? When drawing triangles on a 20-MHz 386, it's
less than one-fifth the speed of the fast convex polygon fill code.
However, most of that time is spent drawing individual pixels; when
Listing 40.2 is replaced with the fast assembly line segment drawing
code in Listing 40.5, performance improves by two and one-half times, to
about half as fast as the fast convex fill code. Even after conversion
to assembly in Listing 40.5, **DrawHorizontalLineSeg** still takes more
than half of the total execution time, and the remaining time is spread
out fairly evenly over the various subroutines in Listing 40.1.
Consequently, there's no single place in which it's possible to greatly
improve performance, and the maximum additional improvement that's
possible looks to be a good deal less than two times; for that reason,
and because of space limitations, I'm not going to convert the rest of
the code to assembly. However, when filling a polygon with a great many
edges, and especially one with a great many active edges at one time,
relatively more time would be spent traversing the linked lists. In such
a case, conversion to assembly (which does a very good job with linked
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);
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
.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
for performance. However, bubble sorts perform well when the data are
already almost sorted, and because of the X coherence of edges from one
scan line to the next, that's generally the case with the AET. An
insertion sort might be somewhat faster, depending on the state of the
AET when any particular sort occurs, but a bubble sort will generally do
just fine.