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

102 lines
6.8 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Chapter 62\
One Story, Two Rules, and a BSP Renderer {#Heading1}
-----------------------------------------
### Taking a Compiled BSP Tree from Logical to Visual Reality {#Heading2}
As I've noted before, I'm working on Quake, id Software's follow-up to
DOOM. A month or so back, we added page flipping to Quake, and made the
startling discovery that the program ran nearly twice as fast with page
flipping as it did with the alternative method of drawing the whole
frame to system memory, then copying it to the screen. We were delighted
by this, but baffled. I did a few tests and came up with several
possible explanations, including slow writes through the external cache,
poor main memory performance, and cache misses when copying the frame
from system memory to video memory. Although each of these can indeed
affect performance, none seemed to account for the magnitude of the
speedup, so I assumed there was some hidden hardware interaction at
work. Anyway, "why" was secondary; what really mattered was that we had
a way to double performance, which meant I had a lot of work to do to
support page flipping as widely as possible.
A few days ago, I was using the Pentium's built-in performance counters
to seek out areas for improvement in Quake and, for no particular
reason, checked the number of writes performed while copying the frame
to the screen in non-page-flipped mode. The answer was 64,000. That
seemed odd, since there were 64,000 byte-sized pixels to copy, and I was
calling **memcpy()**, which of course performs copies a dword at a time
whenever possible. I thought maybe the Pentium counters report the
number of bytes written rather than the number of writes performed, but
fortunately, this time I tested my assumptions by writing an ASM routine
to copy the frame a dword at a time, without the help of **memcpy()**.
This time the Pentium counters reported 16,000 writes.
Whoops.
As it turns out, the **memcpy()** routine in the DOS version of our
compiler (gcc) inexplicably copies memory a byte at a time. With my new
routine, the non-page-flipped approach suddenly became slightly *faster*
than page flipping.
The first relevant rule is pretty obvious: *Assume nothing*. Measure
early and often. Know what's really going on when your program runs, if
you catch my drift. To do otherwise is to risk looking mighty foolish.
The second rule: When you do look foolish (and trust me, it *will*
happen if you do challenging work) have a good laugh at yourself, and
use it as a reminder of Rule \#1. I hadn't done any extra page-flipping
work yet, so I didn't waste any time due to my faulty assumption that
**memcpy()** performed a maximum-speed copy, but that was just luck. I
should have done experiments until I was sure I knew what was going on
before drawing any conclusions and acting on them.
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/i.jpg) *In general, make it a point not to fall into a tightly focused rut; stay loose and think of alternative possibilities and new approaches, and always, always, always keep asking questions. It'll pay off big in the long run. If I hadn't indulged my curiosity by running the Pentium counter test on the copy to the screen, even though there was no specific reason to do so, I would never have discovered the **memcpy()** problem—and by so doing I doubled the performance of the entire program in five minutes, a rare accomplishment indeed.*
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
By the way, I have found the Pentium's performance counters to be very
useful in of information on the performance counters and other aspects
of the Pentium is Mike Schmit's book, *Pentium Processor Optimization
Tools*, AP Professional, ISBN 0-12-627230-1.
Onward to rendering from a BSP tree.
#### BSP-based Rendering {#Heading3}
For the last several chapters I've been discussing the nature of BSP
(Binary Space Partitioning) trees, and in Chapter 60 I presented a
compiler for 2-D BSP trees. Now we're ready to use those compiled BSP
trees to do realtime rendering.
As you'll recall, the BSP compiler took a list of vertical walls and
built a 2-D BSP tree from the walls, as viewed from above. The result is
shown in Figure 62.1. The world is split into two pieces by the line of
the root wall, and each half of the world is then split again by the
root's children, and so on, until the world is carved into subspaces
along the lines of all the walls.
![](images/62-01.jpg)\
**Figure 62.1**  *Vertical walls and a BSP tree to represent them.*
Our objective is to draw the world so that whenever walls overlap we see
the nearer wall at each overlapped pixel. The simplest way to do that is
with the painter's algorithm; that is, drawing the walls in
back-to-front order, assuming no polygons interpenetrate or form cycles.
BSP trees guarantee that no polygons interpenetrate (such polygons are
automatically split), and make it easy to walk the polygons in
back-to-front (or front-to-back) order.
Given a BSP tree, in order to render a view of that tree, all we have to
do is descend the tree, deciding at each node whether we're seeing the
front or back of the wall at that node from the current viewpoint. We
use that knowledge to first recursively descend and draw the farther
subtree of that node, then draw that node, and finally draw the nearer
subtree of that node. Applied recursively from the root of our BSP
trees, this approach guarantees that overlapping polygons will always be
drawn in back-to-front order. Listing 62.1 draws a BSP-based world in
this fashion. (Because of the constraints of the printed page, Listing
62.1 is only the core of the BSP renderer, without the program
framework, some math routines, and the polygon rasterizer; but, the
entire program is on the CD-ROM as DDJBSP2.ZIP. Listing 62.1 is in a
compressed format, with relatively little whitespace; the full version
on the CD-ROM is formatted normally.)