Zip's Double Buffering Tutorial
A neat double buffering tutorial by ZIP.

+-----------------------------------------------+
| File: doublbuf.txt |
| Author: Zip |
| Email: zippy_@hotmail.com |
| Homepage: http://www.angelfire.com/co/zippy15 |
| Date: 10-14-99 |
| Description: Tutorial on double buffering in |
| screen mode 13h |
+-----------------------------------------------+


This document may be freely distributed provided that I am given credit, and
that it is unchanged (including this message). Now read! :)


Intro

If you've been working on a program that requires a lot of colors, but don't
want to use SVGA, then you probably use screen mode 13h (320x200x256). It has
a half-way decent resolution, plenty of colors, and is fairly easy to program.
However, it has one major drawback: it only has one video page. What this
means is that you can't use double-buffering in screen mode 13h, at least not
with PCOPY. This may not be a problem, until you (or someone using your program)
notice that sprites oftenflicker when moved to the upper left corner of the
screen, even when you wait for vertical retrace (WAIT &H3DA, 8).
Double-buffering is the best way to get rid of this flickering. If you don't
know what double-buffering is, it's the process of drawing to an 'invisible'
(active) page, then copying its content to the visual page, thereby preventing
the user from seeing each object being drawn separately. Although you can't use
more than one video page, there is still a way to get around it: use a virtual
screen. All this is, is a buffer the size of the screen that you read and write
to instead of reading and writing to the screen. Then, when you're ready to draw
it all to the screen (like PCOPY, except with our buffer), just draw the entire
buffer at once. There is one problem, though, if you try to draw the contents of
the buffer one pixel at a time, because it's way too slow to be practical.
Therefore, we use a GET/PUT buffer, so we can use QB's 'PUT' command to draw the
buffer on screen very quickly.

Referencing Memory

Before we actually get started with this, you need to understand something
very important: memory. I don't mean just knowing that it's where you store
stuff. What I mean is, you need to know how to reference a particular location
in memory. Memory is referenced by two numbers, a segment and an offset,
written as 'segment:offset'. Think of the address as a street address. The
segment would be the street you live on, and the offset would be the house
number. For example, if your address is '123 blah st.', then the memory
location would be 'blah:123'. Of course, memory is referenced only with
numbers, so you wouldn't put 'blah'. Each offset references a single byte of
memory, and each segment is 16 offsets, or 16 bytes. A 16 byte block of memory
is also sometimes called a 'paragraph'. In this way, you can think of the
memory as a book, where the segment is the paragraph, and the offset is the
line number. Note that by using this method of addressing memory, there are
many ways to reference the same location in memory. For example, 0:26 is the
same as 1:10, assuming they're written in decimal notation. Memory addresses,
however, are usually written in hexadecimal ('hex'; base 16), which uses
characters 0-9 & A-F to represent a number (16 characters to represent a
number, hence the name 'base 16'). This way, we can represent a number up to
255 (the maximum value of one byte) using only 2 digits, as opposed to 3 in
decimal notation. Base conversion of numbers is beyond the scope of this
tutorial, though I may write a tutorial on this subject if there is enough
interest. In QB, numbers in hex are preceeded by '&H' to denote hex notation.
For example, the number 1A would be represented as &H1A in QB. Note that the
'&H' part of the number is usually omitted when talking about a particular
memory address so it will be easier to read, but you should remember that it's
in hex instead of decimal. Now we can represent the address 40:21 as 41:11 or
42:01. To hopefully help make things a little clearer, I'll use a simple
illustration of the first few segments in memory:

Segment: 0               1               2               3               
Offset: 0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
^ ^ ^ ^
0:5 0:13 0:27 0:39
1:03 1:17 1:29
2:07 2:19
3:09


The point of this illustration is to show that there's more than one way to
address the same location in memory, and using hex makes it a lot easier to
figure out, too, since 10 in hex is 16 in decimal.

Setting up the buffer

We'll need a buffer the size of the screen (320*200), but since we DIM it as
an array of integers, we can dimension it half the size of the screen, because
one integer is two bytes. That's (320*200)/2, or 32000. We also need to add 2
bytes for the GET/PUT header, stored at offsets 0 and 1, which contains the
width and height of the sprite. And since we'll need this buffer throughout
the program, we declare it as SHARED.


DIM SHARED BUFFER(32002) AS INTEGER


Now we can't just start reading & writing to this array yet, because it still
doesn't have the right header. Therefore, we set the right screen mode (so
it'll make a valid header for the screen mode we're working with), and use GET
to copy the screen (currently blank) to our buffer, which will give us the
right header. Note that putting SCREEN 13 in qb automatically clears the
screen, so we don't need to use CLS here.


SCREEN 13
GET (0, 0)-(319, 199), BUFFER


Our buffer is now ready to use.

Drawing to the buffer

From here on, we won't use any of QB's built-in graphics functions, except
PUT, which will copy the contents of our buffer to the screen (like PCOPY in
screen mode 7 or 9). The most basic graphics function (and most important), is
a pixel-plotting one (like PSET). To keep the syntax simple and similar to
QB's built-in function, we'll name our sub BPSET. It will take the same
parameters as QB's sub PSET (X, Y, COLOUR). Before we make this sub, however,
we have to know how to properly poke to the buffer. You must understand that
our buffer, just like any other variable, is just part of memory, and
therefore can be referenced with a segment and an offset. Of course, our
buffer has 64002 offsets, so we'll have to figure out which one to write to.
When poking to the screen in SCREEN 13 without using a buffer, you multiply
the Y coordinate by the width of the screen (X resolution), and add the X
coordinate. Then you poke the color value to that offset in segment &HA000.


OFFSET& = Y * 320& + X
DEF SEG = &HA000
POKE OFFSET&, COLOUR
DEF SEG


We'll use the same concept while poking to our buffer, except that it's in a
different segment. Also, the width of the GET/PUT buffer is stored in the
first 2 bytes of the buffer as the number of bits across, so we'll have to
divide that value by 8 before determining the offset to write to. We get the
segment of the buffer using VARSEG(BUFFER(0)). Note that it's important to
specify the first element of the array, instead of putting VARSEG(BUFFER),
because the buffer will overlap many segments, and we only want to know where
it starts. Also, we need to know the offset of where the buffer starts, and we
find that using VARPTR(BUFFER(0)). You can specify any element of the array
for VARPTR, because we can reference them all in the same segment (because the
segments overlap).


W& = BUFFER(0) / 8
O& = VARPTR(BUFFER(2))
OFFSET& = (W& * Y& + X&) + O&


Here, we use LONGs, because the offset is likely to be greater than the
maximum value for an integer (32767), and that would cause an overflow error.
In this code, W& is the width of the buffer, O& is the beginning offset of the
actual image data (right after the header), and X& & Y& are parameters to the
function (the coordinates to draw at). One thing I forgt to mention about
overlapping segments and such: an offset can be no larger than 64K, so you'll
have to change the segment then instead. Because of this, we won't use only
VARSEG(BUFFER(0)) for the segment. Instead, we'll calculate the exact segment
to poke to.


DEF SEG = VARSEG(BUFFER(0)) + INT(OFFSET& / 16)
POKE OFFSET& MOD 16, COLOUR%
DEF SEG


Here, COLOUR% is a parameter to the function, and what it is should be
obvious. I think this section of code is quite simple to figure out, but if
not, then here's what it does: INT(OFFSET& / 16) finds the number of
paragraphs in the offset, and rounds the number down in order to get the
correct segment, and adds it to the segment of the beginning of the buffer
(VARSEG(BUFFER(0))). The OFFSET& MOD 16 divides OFFSET& by 16, then returns
the remainder, which will be a number from 0 to 15. Though this may be
relatively slow, it helps us avoid any overflow errors, which is more
important right now (what good is a super-fast program if it messes up a
lot?). You can work on speeding it up later. And here's our finished BPSET
sub:


SUB BPSET (X&, Y&, COLOUR%)

W& = BUFFER(0) / 8
O& = VARPTR(BUFFER(2))
OFFSET& = (W& * Y& + X&) + O&
DEF SEG = VARSEG(BUFFER(0)) + INT(OFFSET& / 16)
POKE OFFSET& MOD 16, COLOUR%
DEF SEG

END SUB


Reading from the buffer

Using the same concepts presented above for the BPSET sub, we can make a
function to return the color value at a given set of coordinates in our
buffer (like QB's POINT function).


FUNCTION BPOINT% (X&, Y&)

W& = BUFFER(0) / 8
O& = VARPTR(BUFFER(2))
OFFSET& = (W& * Y& + X&) + O&
DEF SEG = VARSEG(BUFFER(0)) + INT(OFFSET& / 16)
BPOINT% = PEEK(OFFSET& MOD 16)
DEF SEG

END FUNCTION


The only difference here is that we PEEK from the location in the buffer
inste
ad of POKEing to it.

Copying the buffer to screen

So far, we can set up and write to our buffer, but one thing is still missing:
we can't view the contents of the buffer! This is where QB's PUT command comes
in.


PUT (0, 0), BUFFER, PSET


Of course, that's quite a lot to type every time you want to copy the buffer
to screen, so we can make a sub that does this, and resembles QB's PCOPY
command.


SUB BCOPY

PUT (0, 0), BUFFER, PSET

END SUB


And that's all there is to it.

Clearing the buffer

If we couldn't clear the buffer, then it probably wouldn't be of much use
to us, because it would still have all the old stuff we drew. To clear the
buffer, simply give each element of BUFFER the value 0, then PUT it on the
screen. We'll name our sub BCLS to keep it similar to qb's syntax.


SUB BCLS

FOR A& = 2 TO 32002
BUFFER(A&) = 0
NEXT A&
PUT (0, 0), BUFFER, PSET

END SUB


Making your own line routine

If all we could do with our buffer is draw a single pixel at a time, then it
wouldn't really be worth using. Therefore, we use our BPSET sub to make more
advanced off-screen graphics functions. Another basic graphics function is
the line.


SUB BLINE (X1%, Y1%, X2%, Y2%, C%)

'(X1, Y1) and (X2, Y2) are the coordinates,
'and C% is the color

DIM DX AS INTEGER, DY AS INTEGER
DIM CX AS INTEGER, CY AS INTEGER, NP AS INTEGER

'DX = delta X (change in X)
'DY = delta Y (change in Y)
'CX = absolute value of DX
'CY = absolute value of DY
'NP = number of pixels between the two points

DIM MX AS SINGLE, MY AS SINGLE
DIM AX AS SINGLE, AY AS SINGLE

'MX = distance to move on X axis for every pixel
'MY = distance to move on Y axis for every pixel
'(-1 <= MX <= 1 and -1 <= MY <= 1)
'AX = actual X position when drawing line
'AY = actual Y position when drawing line

DX = X2% - X1%
DY = Y2% - Y1%

CX = ABS(DX)
CY = ABS(DY)

IF CX > CY THEN
NP = CX
ELSE
NP = CY
END IF

MX = DX / NP
MY = DY / NP

AX = X1%
AY = Y1%

FOR A = 0 TO NP
XX& = AX
YY& = AY
BPSET XX&, YY&, C%
AX = AX + MX
AY = AY + MY
NEXT A

END SUB


Conclusion

With some thinking, you can make your own GET & PUT functions using this
buffer and the basic functions we've already made for using the buffer. You
can also make your own CIRCLE routine (quite simple, actually), and who knows
what all else. If you know assembly language, then you may speed these
functions up even more, but that's beyond the scope of this tutorial. This is
just meant to teach you the concepts, and to get you started. The rest is up
to you. Below is the complete program made throughout this tutorial, in case
you would like to copy it and run it in QB.You are free to use and modify
this program as much as you want, provided that I'm given some credit.


DECLARE FUNCTION BPOINT% (X&, Y&)
DECLARE SUB BLINE (X1%, Y1%, X2%, Y2%, C%)
DECLARE SUB BCOPY ()
DECLARE SUB BPSET (X&, Y&, COLOUR%)

DIM SHARED BUFFER(32002) AS INTEGER
SCREEN 13
GET (0, 0)-(319, 199), BUFFER

BLINE 0, 0, 319, 199, 15
BCOPY

SUB BCOPY

PUT (0, 0), BUFFER, PSET

END SUB

SUB BLINE (X1%, Y1%, X2%, Y2%, C%)

'(X1, Y1) and (X2, Y2) are the coordinates,
'and C% is the color

DIM DX AS INTEGER, DY AS INTEGER
DIM CX AS INTEGER, CY AS INTEGER, NP AS INTEGER

'DX = delta X (change in X)
'DY = delta Y (change in Y)
'CX = absolute value of DX
'CY = absolute value of DY
'NP = number of pixels between the two points

DIM MX AS SINGLE, MY AS SINGLE
DIM AX AS SINGLE, AY AS SINGLE

'MX = distance to move on X axis for every pixel
'MY = distance to move on Y axis for every pixel
'(-1 <= MX <= 1 and -1 <= MY <= 1)
'AX = actual X position when drawing line
'AY = actual Y position when drawing line

DX = X2% - X1%
DY = Y2% - Y1%

CX = ABS(DX)
CY = ABS(DY)

IF CX > CY THEN
NP = CX
ELSE
NP = CY
END IF

MX = DX / NP
MY = DY / NP

AX = X1%
AY = Y1%

'Note: If you're using screen mode 13h, you can speed this part up if you put
'DEF SEG = &HA000
'then use
'POKE AY * 320 + AX, C%
'instead of using PSET. I only used PSET here so it would work in any graphics
'mode.

FOR A = 0 TO NP
XX& = AX
YY& = AY
BPSET XX&, YY&, C%
AX = AX + MX
AY = AY + MY
NEXT A

END SUB

FUNCTION BPOINT% (X&, Y&)

W& = BUFFER(0) / 8
O& = VARPTR(BUFFER(2))
OFFSET& = (W& * Y& + X&) + O&
DEF SEG = VARSEG(BUFFER(0)) + INT(OFFSET& / 16)
BPOINT% = PEEK(OFFSET& MOD 16)
DEF SEG

END FUNCTION

SUB BPSET (X&, Y&, COLOUR%)

W& = BUFFER(0) / 8
O& = VARPTR(BUFFER(2))
OFFSET& = (W& * Y& + X&) + O&
DEF SEG = VARSEG(BUFFER(0)) + INT(OFFSET& / 16)
POKE OFFSET& MOD 16, COLOUR%
DEF SEG

END SUB


Contact Info

And that's it for this tutorial. Hopefully this will help someone reduce
flicker in their program and perhaps even learn something. :)

If you have any questions, comments, etc., you can contact me via email at
zippy_@hotmail.com, or on icq at 48620353.


ZipsDoubleBufferingTutorial - page last edited 2003-08-11 01:56:07 by 81.203.197.39 (home) (edit)
Blast WIKI - by RoboticBoy - edited and tweaked for our evil purposes by Hexadecimal Disaster