Double Buffering Concepts
What it is and how it is used.

Introduction

Instead drawing directly on your screen, basicly you blit stuff to a buffer, which is a virtual screen. Then, when you're done, you copy your buffer to screen to make changes visible, usually synchronizing it with the VGA refresh (WAIT &H3DA,8: WAIT &H3DA,8,8 stuff).

A buffer is just a piece of memory. It is very handy making it just like your screen, so copy is direct. VGA mode13h screen takes 64000 contiguous bytes, so that's all what we need. The easiest way of allocating 64000 contiguous bytes in QB is DIMming an array that takes 64000 bytes. For example, an integer array of 32000 elements (each element is 2 bytes, so 32000x2=64000):

DIM myBuffer%(31999)  ' 0 to 31999 -> 32000 elements





Implementation

You have three choices to implement a double buffer:

a.- The easiest: use a lib.

b.- Use GET and PUT along with Plasma357's routines (SetVideoSeg). SetVideoSeg just changes the VGA segment (&HA000) so QB thinks VGA is where you tell him it is (i.e. your buffer) so graphic stuff will be drawn on your buffer and not on screen.

SetVideoSegInScreenThirteen

' videoSeg by Plasma357
' Changes QB's active video segment for SCREEN 13
'
' (Works both compiled and in the IDE)
'
' Note: Text is still output to segment A000, but all graphics functions will
' use the new segment specified.

DEFINT A-Z

DECLARE SUB setVideoSeg (Segment)

' Here's a useless example that just shifts the segment 16 bytes. Not very
' useful, but you get the picture. ;) You can easily change the segment to
' the EMS pageframe or a buffer in conventional memory.

SCREEN 13

SUB setVideoSeg (Segment) STATIC

DEF SEG

IF videoAddrOff& = 0 THEN ' First time the sub is called

' We need to find the location of b$AddrC, which holds the graphics
' offset (b$OffC) and segment (b$SegC). Since b$AddrC is in the default
' segment, we can find it by setting it to a certain value, and then
' searching for that value.

SCREEN 13 ' Set b$SegC to A000 (00A0 in memory)
PSET (160, 100), 0 ' Set b$OffC to 7DA0 (not needed in the IDE)

FOR Offset& = 0 TO 32764 ' Search for b$AddrC, which is
IF PEEK(Offset&) = &HA0 THEN ' in the default segment and
IF PEEK(Offset& + 1) = &H7D THEN ' should have a value of
IF PEEK(Offset& + 2) = &H0 THEN ' A0 7D 00 A0.
IF PEEK(Offset& + 3) = &HA0 THEN
videoAddrOff& = Offset& + 2 ' If we found it, record the
EXIT FOR ' offset of b$SegC and quit
END IF ' looking. (Oddly, changing
END IF ' the b$OffC doesn't seem to
END IF ' do anything, so this is why
END IF ' this sub only changes b$SegC)
NEXT

END IF

' Change b$SegC to the specified Segment

POKE videoAddrOff&, Segment AND &HFF
POKE videoAddrOff& + 1, (Segment AND &HFF00&) \ &H100

END SUB


So when you wanna start drawing to your buffer, just change the video seg to your buffer's segment:

setVideoSeg VARSEG(buffer%(0))
' draw stuff here


To change it back to the original VGA segment, just...

setVideoSeg &HA000


c.- The hardest: Code your own blitters that draw to a buffer instead of drawing to screen. (Check ZipsDoubleBufferingTutorial for more info)




Making the buffer visible

To copy the buffer to video memory you can do three things, again:

a.- The easiest: Use a lib.
b.- You can grab somewhere a short ASM code embedded into a SUB that copies memory. It is called MemCopy and you can get it in the ABC packets. You can use SETVIDEOSEG to draw in your buffer, and MEMCOPY to copy to the actual screen.

Memcopy routine
'===========================================================================
' Subject: PATCHED MEMCOPY ROUTINE Date: 02-27-98 (00:57)
' Author: Jonathan L. Leger Code: QB, QBasic, PDS
' Origin: leger@earthlink.net Packet: MEMORY.ABC
'===========================================================================
defInt A-Z

DECLARE SUB initMemCopy ()
DECLARE SUB memCopy (fromseg%, fromoffset%, toseg%, tooffset%, bytes%)
DECLARE SUB fillChar (segment%, offset%, value%, bytes%)

DIM SHARED memCopy.ASM AS STRING
Dim Array1(1 To 1000) As Integer
Dim Array2(1 To 1000) As Integer

initMemCopy

'*** Fill the first array.
For Value = 1 To 1000: Array1(Value) = Value: Next Value

'*** Copy the contents of Array1() into Array2()
a1seg = VARSEG(Array1(1)) '*** Segment location of Array1()
a1off = VARPTR(Array1(1)) '*** Offset of Array1()
a2seg = VARSEG(Array2(1)) '*** Segment location of Array2()
a2off = VARPTR(Array2(1)) '*** Offset of Array2()
bytes = 2000 '*** Number of bytes (integer = 2 bytes)
memCopy a1seg, a1off, a2seg, a2off, bytes

'*** Show second array's contents.
For Value = 1 To 1000: Print Array2(Value);: Next Value

End



defSng A-Z
Sub initMemCopy()

memCopy.ASM = ""
memCopy.ASM = memCopy.ASM + Chr$(85) 'PUSH BP
memCopy.ASM = memCopy.ASM + Chr$(137) + Chr$(229) 'MOV BP,SP
memCopy.ASM = memCopy.ASM + Chr$(30) 'PUSH DS
memCopy.ASM = memCopy.ASM + Chr$(139) + Chr$(70) + Chr$(10) 'MOV AX,[BP+0A]
memCopy.ASM = memCopy.ASM + Chr$(142) + Chr$(192) 'MOV ES,AX
memCopy.ASM = memCopy.ASM + Chr$(139) + Chr$(70) + Chr$(14) 'MOV AX,[BP+0E]
memCopy.ASM = memCopy.ASM + Chr$(142) + Chr$(216) 'MOV DS,AX
memCopy.ASM = memCopy.ASM + Chr$(139) + Chr$(118) + Chr$(12) 'MOV SI,[BP+0C]
memCopy.ASM = memCopy.ASM + Chr$(139) + Chr$(126) + Chr$(8) 'MOV DI,[BP+08]
memCopy.ASM = memCopy.ASM + Chr$(139) + Chr$(78) + Chr$(6) 'MOV CX,[BP+06]
memCopy.ASM = memCopy.ASM + Chr$(243) 'REPZ
memCopy.ASM = memCopy.ASM + Chr$(164) 'MOVSB
memCopy.ASM = memCopy.ASM + Chr$(31) 'POP DS
memCopy.ASM = memCopy.ASM + Chr$(93) 'POP BP
memCopy.ASM = memCopy.ASM + Chr$(203) 'RETF

End Sub

defInt A-Z
Sub memCopy(fromseg%, fromoffset%, toseg%, tooffset%, bytes%)

DEF SEG = VARSEG(memCopy.ASM)
Call Absolute(byVal fromseg%, byVal fromoffset%, byVal toseg%, byVal tooffset%, byVal bytes%, SADD(MemCopy.ASM))
DEF SEG

End Sub


Let's say that your buffer is Buffer%(). To copy it to screen, just use:

memCopy VARSEG(Buffer%(0)), VARPTR(Buffer%(0)), &HA000, 0, 64000


c.- The hardest: Make your array compatible with the GET/PUT format and use PUT to copy it to screen. This involves doing some calculations, but SETVIDEOSEG won't work straightforward (without having to play a little bit with it). All that you need is skip some bytes at the beginning of your buffer array when you use SETVIDEOSEG:

c.1.- PUT needs two integers to know how big your image is. The first one is the width*8, and the second one is the height. The idea is that you set up your array in a way that the image begins in the byte 16, so you can use SETVIDEOSEG VARSEG(buffer%(0))+1. This is done 'cause we need to store those two integers for PUT, and you don't want them to be overwritten when you change your video segment. Therefore, we dim our array that way:

DIM buffer%(32009)
buffer%(6) = 2560 ' 320*8
buffer%(7) = 200

bufferSegment% = VARSEG(buffer%(0)) + 1


c.2.- To draw stuff to our buffer, we just call SETVIDEOSEG:

setVideoSeg bufferSegment%
' Draw Stuff


c.3.- To make everything visible, we use PUT:

setVideoSeg &HA000   ' We point to VGA again.
PUT (0, 0), buffer%(6), PSET





And, to finish with...

This works 'cause while you are drawing stuff it is not seen, so you can clear the buffer and put your sprites again without being noticed by the player. When you copy the buffer to screen the objects will be completely drawn.

The basic double-buffer loop can be as follows:

DO
' Do game (movement and stuff).
' Draw stuff on buffer.
' Wait to VGA refresh (WAIT &H3DA,8: WAIT &H3DA,8,8)
' Copy buffer to screen
LOOP





An Example

This example uses SetVideoSeg to draw to the buffer and PUT to copy it to the screen.

DEFINT A-Z
DECLARE SUB setVideoSeg (Segment)

SCREEN 13

' *** HERE BEGINS THE EXAMPLE BY NA_TH_AN ***

' Initialize buffer [thanks rel]:
DIM myBuffer%(32009)
myBuffer%(6) = 2560
myBuffer%(7) = 200
myBufferSegment% = VARSEG(myBuffer%(0)) + 1

x = 160: y = 100: mx = 1: my = 1

' Main loop
WHILE INKEY$ = ""
' 1. Move stuff.
x% = x% + mx%
y% = y% + my%
IF x% = 0 OR x% = 304 THEN mx% = -mx%
IF y% = 0 OR y% = 184 THEN my% = -my%

' 2. Draw stuff into buffer.
setVideoSeg myBufferSegment%
LINE (0, 0)-(319, 199), 5, BF ' This erases :P.

LINE (0, 0)-(319, 199), 11 ' Crappy background
LINE (0, 199)-(319, 0), 11
LINE (0, 0)-(319, 199), 11, B

LINE (x%, y%)-(15 + x%, 15 + y%), 14, B ' Crappy sprite ;)
LINE (x% + 1, y% + 1)-(14 + x%, 14 + y%), 1, BF

' 3. Synchronize with VGA:
WAIT &H3DA, 8: WAIT &H3DA, 8, 8

' 4. Make everything visible:
setVideoSeg &HA000
PUT (0, 0), myBuffer%(6), PSET
WEND

SUB setVideoSeg (Segment) STATIC ' By Plasma357

DEF SEG

IF videoAddrOff& = 0 THEN ' First time the sub is called

' We need to find the location of b$AddrC, which holds the graphics
' offset (b$OffC) and segment (b$SegC). Since b$AddrC is in the default
' segment, we can find it by setting it to a certain value, and then
' searching for that value.

SCREEN 13 ' Set b$SegC to A000 (00A0 in memory)
PSET (160, 100), 0 ' Set b$OffC to 7DA0 (not needed in the IDE)

FOR Offset& = 0 TO 32764 ' Search for b$AddrC, which is
IF PEEK(Offset&) = &HA0 THEN ' in the default segment and
IF PEEK(Offset& + 1) = &H7D THEN ' should have a value of
IF PEEK(Offset& + 2) = &H0 THEN ' A0 7D 00 A0.
IF PEEK(Offset& + 3) = &HA0 THEN
videoAddrOff& = Offset& + 2 ' If we found it, record the
EXIT FOR ' offset of b$SegC and quit
END IF ' looking. (Oddly, changing
END IF ' the b$OffC doesn't seem to
END IF ' do anything, so this is why
END IF ' this sub only changes b$SegC)
NEXT

END IF

' Change b$SegC to the specified Segment

POKE videoAddrOff&, Segment AND &HFF
POKE videoAddrOff& + 1, (Segment AND &HFF00&) \ &H100

END SUB


na_th_an


DoubleBufferingGeneral - page last edited 2004-02-11 08:36:13 by 172.202.56.16 (home) (edit)
Blast WIKI - by RoboticBoy - edited and tweaked for our evil purposes by Hexadecimal Disaster