If you like graphics the blitter is going to be your best friend. Back in the early days graphics used to be written to and from a memory location in RAM by the processor. The PC used a videosync timer to poll the memory and paint the graphics to the screen. You had to code carefully to be sure your drawing was finished before the videosync kicked in.
Blitting hardware was developed to allow large areas of graphics to be copied and pasted in memory without requiring processor cycles. This vastly improved animation and drawing that could be achieved between videosync events, It's kinda old fashioned, my Zx81 didn't have a blitter but my Amstrad and Commodore sure did.
Graphics have evolved still more and now its all "surfaces" and openGL rendering or DirectX rendering, but you can still have a lot of fun with the blitter.
Display Timing
How fast can we show blitted graphics? Notice I said "show", a moden PC's graphics card typically renders screen images at 60hz, or a frame every 16.6ms. Now the blitter can blit much much faster than this but there is little point in blitting for blittings sake if the graphic image is only shown once every 16.6ms.
So its best to use the Liberty BASIC TIMER statement to control a drawing loop to slow things down and not waste time drawing graphics to the buffer that will never be seen.
While the TIMER uses millisecond values you will find that 16.6ms is the smallest value it can discern. Run the code below and see how the TIMER value jumps in 16.6ms increments. This is because Windows updates it's clock at 60hz.
What is the lesson? Don't try and run your TIMER loop faster than 17ms, the TIMER statement won't run any faster and even if it did you will be drawing graphics to the buffer that will never be seen.
In actual fact animation will run perfectly well at lower speed. The speed of your processor and graphics card will determine what is achievable but stay in tune with your PC, anything between 17ms and 100ms provide watchable animation.
for delay=1to60
timenow=time$("ms")timer1,[done]wait[done]print"Delay should be 1ms Delay is ";timenow-time$("ms")next delay
Blitter Functionality
Blitting swaps one area of graphic for another. The blitter can merge, overwrite or transparently combine the graphics. It can also flip, mirror, stretch or shrink the image as it does so. Animation is achieved by moving one image relative to the other.
Blitting is usually associated with animation or graphics transformations though it is equally happy stretching, blending, flipping or compressing static images.
Blitter code
You will need to cut and paste these blitter functions to the foot of each of the following code examples.
'=============================Window and DC functions=================================Function GetDC(hWnd)CallDLL#user32,"GetDC",_
hWnd Asulong,_ 'window or control handle
GetDC Asulong'returns device contextEndFunctionSub ReleaseDC hWnd, hDC
CallDLL#user32,"ReleaseDC",_
hWnd Asulong,_ 'window or control handle
hDC Asulong,_ 'handle of DC to delete
result AsLongEndSubFunction CreateCompatibleDC(hDC)CallDLL#gdi32,"CreateCompatibleDC",_
hDC Asulong,_ 'window DC
CreateCompatibleDC Asulong'memory DCEndFunctionSub DeleteDC hDC
CallDLL#gdi32,"DeleteDC",_
hDC Asulong,_ 'memory DC to delete
r AslongEndSubSub StretchBlt hDCdest,x,y,w,h,hDCsrc,x2,y2,w2,h2
CallDLL#gdi32,"SetStretchBltMode",_
hDCdest Asulong,_ 'device context
_COLORONCOLOR AsLong,_ 'color reduction mode
RESULT AsLongCallDLL#gdi32,"StretchBlt",_
hDCdest Asulong,_ 'destination
x AsLong,_ 'destination x pos
y AsLong,_ 'destination y pos
w AsLong,_ 'destination width desired
h AsLong,_ 'destination height desired
hDCsrc Asulong,_ 'source
x2 AsLong,_ 'x location to start from source
y2 AsLong,_ 'y location to start from source
w2 AsLong,_ 'width desired from source
h2 AsLong,_ 'height desired from source
_SRCCOPY Aslong,_ 'dwRasterOperation
RESULT AslongEndSubSub TransparentBlt hDCdest,x,y,w,h,hDCsrc,x2,y2,w2,h2,crTransparent
calldll#m,"TransparentBlt",_
hDCdest AsuLong,_ 'destination
x AsLong,_ 'destination x pos
y AsLong,_ 'destination y pos
w AsLong,_ 'destination width desired
h AsLong,_ 'destination height desired
hDCsrc AsuLong,_ 'source
x2 AsLong,_ 'x location to start from source
y2 AsLong,_ 'y location to start from source
w2 AsLong,_ 'width desired from source
h2 AsLong,_ 'height desired from source
crTransparent asulong,_ 'color to make transparent
result aslongendsubFunction SelectObject(hDC,hObject)CallDLL#gdi32,"SelectObject",_
hDC Asulong,_ 'memory device context
hObject Aslong,_ 'handle of object
SelectObject Aslong'returns previously selected objectEndFunctionFunction SetPixel(hDc,x,y,rgbColor)CallDll#gdi32,"SetPixel",_
hDc asUlong,_ 'the handle of the Device context from GetDC
x aslong,_ 'the x coordinate to draw the pixel
y aslong,_ 'the y coordinate to draw the pixel
rgbColor aslong,_
SetPixel aslongEndFunctionSub SetBkMode hDC, flag
'1=transparent'2=opaqueCallDLL#gdi32,"SetBkMode",hDC Asulong,_
flag Aslong, RESULT AslongEndSubSub TextOutA hDc,X,Y,text$
lengthtext=len(text$)calldll#gdi32,"TextOutA", _
hDc asulong,_ 'device context of window or graphicbox
X aslong,_ 'x origin of text
Y aslong,_ 'y origin of texttext$ asptr,_ 'text string to display
lengthtext aslong,_'length of text string
result aslong'nonzero if successfulEndSubsub SetTextColor hDc,crColor
calldll#gdi32,"SetTextColor", _
hDc asulong,_ 'device context of window or control
crColor aslong,_ 'long integer color value
result aslong'returns previous text color, if successfulEndSub
How fast is the Blitter?
Lets run some unrestricted code on your machine to see what blitting performance you get. The code will create a copy of your screen, select the current screen image into that and then blit it back to the screen repeatedly. It is important to remember that the blitter is not blitting directly to the screen that you see in front of you but to the memory that defines that screen. Lets call it the screen buffer. The blitting will be very fast, you won't see anything but it is happening. Run it now.
Fantastic result? unbelievably fast? you bet. On my machine which is a middle of the road laptop I rendered the half megabyte of graphics in 420 milliseconds, that's over 2300 frames per second, wow!
Now of course you didn't see anything. First because it was a black image blitted over a black image but more importantly the screen buffer was rendered to the LCD or Glass display much more slowly at 60 frames per second, very much slower indeed. So even if the image was changing you would only see 38 of those 2300 frames we rendered to memory.
That's great news, it means that if we need to, we can blit in massive amounts of graphics between video display updates. But neither should we let the blitter run unchecked.
nomainwinWindowWidth=600WindowHeight=600UpperLeftX=(DisplayWidth-WindowWidth)/2UpperLeftY=(DisplayHeight-WindowHeight)/2graphicbox#1.g,50,80,500,300textbox#1.t,50,390,500,25open"Blitter"for graphics_nf_nsb as#1print#1,"trapclose [quit]"' set up our bitmaps, open device contexts and store our bitmaps in them' bDC is our screen buffer, (DC handle to the graphicsbox)print#1.g,"down; fill black"print#1.g,"getbmp bmp 0 0 500 300"
bDC=GetDC(hwnd(#1.g))' mDC is a copy of the screen in memory
mDC=CreateCompatibleDC(bDC)
hBitmap=hbmp("bmp")
oldBmp=SelectObject(mDC,hBitmap)[timeloop]
now=time$("ms")for n=1to1000scan' flip the memory image to the screencall StretchBlt,bDC,0,0,500,300,mDC,0,0,500,300next n
t=time$("ms")-now
print#1.t,"Rendered 439Mb of graphics in ";t;" Milliseconds, ";int(1000/t*1000);" FPS"wait[quit]unloadbmp"bmp"call ReleaseDC hwnd(#1), gDC
call DeleteDC mDC
close#1end
Device Contexts
The blitter is made available to us via API calls from Liberty BASIC. Obviously before we can blit anything we need somewhere to blit from and somewhere to blit to. Typically we would blit to the screen buffer. So how do we access this buffer?
We need to find it's address and would use an API call to do so.
gDC=GetDC(hwnd(#1.g))Function GetDC(hWnd)CallDLL#user32,"GetDC",_
hWnd Asulong,_ 'window or control handle
GetDC Asulong'returns device contextEndFunction
This obtains a ulong number which is the pointer to the Device Context (DC) that Windows created for our graphicbox. So the screen buffer is in fact the DC that Windows has created behind the scenes.
Now we create a copy of that DC.
mDC=CreateCompatibleDC(gDC)Function CreateCompatibleDC(hDC)CallDLL#gdi32,"CreateCompatibleDC",_
hDC Asulong,_ 'window DC
CreateCompatibleDC Asulong'memory DCEndFunction
Then we fill it with some graphics by getting a handle to our "bmp" image and selecting it into the new DC.
And that's all we need to start blitting! DCs are amorphous things, if you select in a large bmp you can have lots of graphics in one location to blit from.
Once you have finished with a DC you must release and delete it.
call ReleaseDC hwnd(#1), gDC
call DeleteDC mDC
Sub ReleaseDC hWnd, hDC
CallDLL#user32,"ReleaseDC",_
hWnd Asulong,_ 'window or control handle
hDC Asulong,_ 'handle of DC to delete
result AsLongEndSubSub DeleteDC hDC
CallDLL#gdi32,"DeleteDC",_
hDC Asulong,_ 'memory DC to delete
r AsLongEndSub
That's a whistle stop tour of DCs, check the following link for help on API and DCs under the GDI banner.
Lets draw ourselves some graphics to blit from. We use the left 225 pixels of the screen to draw some stars and then replicate that in the next 225 pixels giving us a 450 pixel image that will scroll without an obvious end. Confused? Don't worry the tutorial is about blitting not game graphics (That might be next).
Then I draw a little circle in the remaining 50 pixels. Use the debugger to step through and see what's happening. Now when I start blitting I take the first 225 pixels from the memory DC and blit them to the buffer DC but stretch them width ways up to 500 pixels. In this way you cut out little parts of the memory DC and fill the buffer DC. Next iteration I move the source 5 pixels to the right in the graphics DC and so repetitively move the star background to the left.
Then I cut out the little circle and blit it a couple of times same size into the buffer. Net result a scrolling background with two sprites moving about. Well they would be sprites if I had used TransparentBlt, notice that the circle backgrounds overwrite when they cross over. If I had used TransparentBlt you would just see the white circles.
nomainwinWindowWidth=600WindowHeight=600UpperLeftX=(DisplayWidth-WindowWidth)/2UpperLeftY=(DisplayHeight-WindowHeight)/2graphicbox#1.g,50,80,500,300textbox#1.t,50,390,500,25open"Blitter"for graphics_nf_nsb as#1print#1,"trapclose [quit]"' set up our bitmaps, open device contexts and store our bitmaps in them' bDC is our screen buffer, (DC handle to the graphicsbox)print#1.g,"down; fill black ; color white"for x =1to50print#1.g,"place ";int(rnd(0)*250);" ";int(rnd(0)*300)print#1.g,"size ";int(rnd(0)*4)print#1.g,"circle ";int(rnd(0)*3)next x
print#1.g,"getbmp bmp 0 0 225 300"print#1.g,"drawbmp bmp 225 0"print#1.g,"place 475 40 ; size 1 ;circle 20"unloadbmp"bmp"print#1.g,"getbmp bmp 0 0 500 300"
bDC=GetDC(hwnd(#1.g))' mDC is a copy of the screen in memory
mDC=CreateCompatibleDC(bDC)
hBitmap=hbmp("bmp")
oldBmp=SelectObject(mDC,hBitmap)' set up a repeating loop to draw our graphics
x=-1
y=-1timer17,[timedloop]wait' draw from the memory to the buffer[timedloop]
x=x+1if x=225then x=0
y=y+1if y=300then y=0call StretchBlt,bDC,0,0,500,300,mDC,x,0,225,300call StretchBlt,bDC,250,y,40,40,mDC,455,20,40,40call StretchBlt,bDC,250,300-y,40,40,mDC,455,20,40,40wait[quit]unloadbmp"bmp"call ReleaseDC hwnd(#1), gDC
call DeleteDC mDC
close#1end
So there are several call you can make BitBlt, StretchBlt PrlBlt and TransparentBlt each has it's own strength some can flip and mirror some can't, one can do it transparently, basically pick your blitter call by the functionality you wish.
These API calls are well documented elsewhere so I'm not covering the detail here.
Double Buffering
Double buffering is something you will eventually come to. When you render large or complex image manipulations or start to render text you will find that the image starts to flicker. Text drawing in particular needs a double buffer. What happens here is that we draw all the complex graphics to an intermediate DC and then on a regular basis blit the contents to the buffer DC and leave it alone. This ensures that the buffer DC has a stable copy of what we want displayed and ensures rock solid graphics.
The flicker is rooted in how Windows updates it's screen, the videosync is not made available to us as many windows may wish redrawn or updated. So the screen refresh happens randomly as far as we are concerned. Right in the middle of drawing a line or rendering some text. But because we can render a single screen image so quickly with the blitter it rarely, if ever, is half drawn when the refresh occurs.
One of the other great things about blitting is that you do not use up graphics memory, each DC consumes a finite amount of memory but after that you are just blitting between them and no other graphics memory is consumed.
nomainwinWindowWidth=600WindowHeight=600UpperLeftX=(DisplayWidth-WindowWidth)/2UpperLeftY=(DisplayHeight-WindowHeight)/2graphicbox#1.g,50,80,500,300textbox#1.t,50,390,500,25open"Blitter"for graphics_nf_nsb as#1print#1,"trapclose [quit]"' set up our bitmaps, open device contexts and store our bitmaps in themprint#1.g,"down; fill black ; color white"for x =1to50print#1.g,"place ";int(rnd(0)*250);" ";int(rnd(0)*300)print#1.g,"size ";int(rnd(0)*4)print#1.g,"circle ";int(rnd(0)*3)next x
print#1.g,"getbmp bmp 0 0 225 300"print#1.g,"drawbmp bmp 225 0"print#1.g,"place 475 40 ; size 1 ;circle 20"unloadbmp"bmp"print#1.g,"getbmp bmp 0 0 500 300"print#1.g,"getbmp bkg 0 0 500 300"' bDC is our screen buffer, (DC handle to the graphicsbox)
bDC=GetDC(hwnd(#1.g))' mDC is a copy of the screen in memory (double buffer)
mDC=CreateCompatibleDC(bDC)
hBitmap=hbmp("bmp")
oldBmp=SelectObject(mDC,hBitmap)' gDC is a copy of the screen in memory to store reusable graphics in' our graphics pallete if you like.
gDC=CreateCompatibleDC(bDC)' we copy the screen but fill it with graphics of our choice' and low and behold this amorphous data object assumes the' size of the bmp we select into it
hBitmap=hbmp("bkg")
oldBmp=SelectObject(gDC,hBitmap)' set up a repeating loop to draw our graphics
x=-1
y=-1timer17,[timedloop]wait[timedloop]scan' first thng we do now is draw from the memory to the buffercall StretchBlt,bDC,0,0,500,300,mDC,0,0,500,300' now draw from the graphics resource to the memory
x=x+1if x=225then x=0
y=y+1if y=300then y=0call StretchBlt,mDC,0,0,500,300,gDC,x,0,225,300call StretchBlt,mDC,250,y,40,40,gDC,455,20,40,40call StretchBlt,mDC,250,300-y,40,40,gDC,455,20,40,40wait[quit]unloadbmp"bmp"call ReleaseDC hwnd(#1), gDC
call DeleteDC mDC
close#1end
Adding Text
We use another API call to render text to our DC. This action in itself triggers the need for the double buffer. Here we create the same moving background but ignore the circle sprite and instead render two sets of text. The text API calls firstly set the text background color to be transparent. Then they set the text color and render the text.
nomainwin'open a window and graphicboxWindowWidth=500WindowHeight=300graphicbox#1.g,0,0,500,300open"Blitting"for graphics_nf_nsb as#1print#1,"trapclose [quit]"' set up our device contexts (DCs, copys of the screen) and' store our various bitmaps in them' once we know the "handles" of our DC's we can operate' on them.print#1.g,"down; fill black ; color white"for x =1to50print#1.g,"place ";int(rnd(0)*250);" ";int(rnd(0)*300)print#1.g,"size ";int(rnd(0)*4)print#1.g,"circle ";int(rnd(0)*3)next x
print#1.g,"getbmp bmp 0 0 225 300"print#1.g,"drawbmp bmp 225 0"print#1.g,"place 475 40 ; size 1 ;circle 20"unloadbmp"bmp"print#1.g,"getbmp bmp 0 0 500 300"print#1.g,"getbmp bkg 0 0 500 300"' bDC is our screen buffer
bDC=GetDC(hwnd(#1.g))' mDC is a copy of the screen buffer in memory used as a double buffer' to collate our drawing operations prior to displaying.
mDC=CreateCompatibleDC(bDC)' once you have the DC you can fill it with graphics
hBitmap=hbmp("bmp")
oldBmp=SelectObject(mDC,hBitmap)' as this is the main drawing screen we set transparent text drawing' on. Any Text drawing will have a transparent background.call SetBkMode,mDC,1' gDC is a copy of the screen in memory to store reusable graphics in' our graphics pallete if you like.
gDC=CreateCompatibleDC(bDC)' we copy the screen but fill it with the bkg graphics
hBitmap=hbmp("bkg")
oldBmp=SelectObject(gDC,hBitmap)' variables
x=-1
t1=100
t2=150
y=15' start timed drawing looptimer17,[draw]wait[draw]' check for mouse or keyboard eventsscan' blit the double buffer to the buffer so that it is visible and stable for as long as possible' StretchBlt takes data from the resource DC gDC and stretches it to fill the target DC, mDC.' There is also TransparentBlt if you wish to draw transparent data. You can flip, mirror' and squish the graphics if you choose the correct Blit operation.call StretchBlt,bDC,0,0,500,300,mDC,0,0,500,300' now start to redraw everything on the buffer' first slip the background xy a little to the left
x=x+1if x=225then x=0' now draw the backgrond from gDC stretching it to fill mDC' the source, gDC can be flipped, reversed as it is drawn if you choose negative' values.call StretchBlt,mDC,0,0,500,300,gDC,x,0,225,300' slip the text1 xy to the left
t1=t1+1if t1>500then t1=-100' set the txt color and draw itcall SetTextColor,mDC,(255*256*256)+(0*256)+(0)'bluetext$ ="Text string to display."call TextOutA,mDC,t1,150,text$
' slip the text2 xy up
y=y-1if y<-20then y=320' set thesecond txt color and draw itcall SetTextColor,mDC,(0*256*256)+(255*256)+(0)'greentext$ ="Yet more text."call TextOutA,mDC,t2,y,text$
wait[quit]timer0call ReleaseDC hwnd(#1.g), bDC
call ReleaseDC hwnd(#1.g), mDC
call ReleaseDC hwnd(#1.g), gDC
call DeleteDC bDC
call DeleteDC mDC
call DeleteDC gDC
unloadbmp"bkg"unloadbmp"bmp"close#1end
Seasick?
Ok scrolling stars and text gets a bit boring. Do you get seasick? Check out this rolling ocean. Use the mouse or arrow keys to turn and speed up or slow down. It kinda gives the impression of being on a rolling ocean swell. The ocean.bmp has four strips for the horizon which I blit into the top of the picture depending on what the heading is. Then I blit four copies of the sea section. I roll these up and down using a sine curve and I also roll them forward to create more movement. The further away section rolls least and they double up their movement the nearer the bottom of the screen they are.
Its just like having multiple backgrounds in the sprite engine. There is lots of time to draw sprites on top of this moving background. Doing so creates a pretty cool game environment.
Copy this bmp to your own PC
nomainwin
timervalue=42
true=1
false=0
heading=0
turn=0
speed=0
midx=400
midy=300
posx=40
posy=40' now open our window as a full screen popup window and set its event labelsWindowWidth=800WindowHeight=600UpperLeftX=(DisplayWidth-WindowWidth)/2UpperLeftY=(DisplayHeight-WindowHeight)/2graphicbox#1.g,0,0,800,600open"Ocean"for window_popup as#1print#1,"trapclose [quit]"print#1.g,"when mouseMove [movemouse]"' open the dll file used for TransparentBltopen"msimg32.dll"fordllas#m
' set up our bitmaps, open device contexts and store our bitmaps in themprint#1.g,"down ; fill black"print#1.g,"getbmp bmp 0 0 800 600"loadbmp"ocean","ocean.bmp"' bDC is our screen buffer, (DC handle to the graphicsbox)
bDC=GetDC(hwnd(#1.g))' mDC is a copy of the screen in memory (double buffer)
mDC=CreateCompatibleDC(bDC)
hBitmap=hbmp("bmp")
oldBmp=SelectObject(mDC,hBitmap)' gDC is a copy of the screen in memory to store reusable graphics in' our graphics pallete if you like.
gDC=CreateCompatibleDC(bDC)' we copy the screen but fill it with graphics of our choice' and low and behold this amorphous data object assumes the' size of the bmp we select into it
hBitmap=hbmp("ocean")
oldBmp=SelectObject(gDC,hBitmap)' start the game timertimer timervalue ,[drawloop]wait' the main drawing loop[drawloop]' read keyboard and mouse eventsscan' flip the mDC to the bDC so that it is visible and stable for as long as possiblecall StretchBlt,bDC,0,0,800,600,mDC,0,0,800,600' now start to redraw everything to mDC from gDC' draw the horizon
heading=heading+turn
if heading<0then heading=heading+3200if heading>3199then heading=heading-3200
horizonX=heading-(int(heading/800))*800
horizonY=int(heading/800)*100
horizonZ=horizonY+100if horizonZ=400then horizonZ=0call StretchBlt,mDC,0,0,800-horizonX,200,gDC,horizonX,horizonY,800-horizonX,100call StretchBlt,mDC,800-horizonX,0,horizonX,200,gDC,0,horizonZ,horizonX,100' draw the sea
wave1=int(sin(roll/57.29577951)*(40-speed))
wave2=int(wave1/2)
wave3=int(wave1/4)
wave4=int(wave1/8)
roll=roll+8+speed
if roll>=360then roll=0
sea1=sea1-(speed/2)
sea2=sea2-(speed/4)
sea3=sea3-(speed/8)
sea4=sea4-(speed/16)if sea1<400then sea1=500if sea2<400then sea2=500if sea3<400then sea3=500if sea4<400then sea4=500
turn4=turn4+(turn/2)
turn3=turn3+(turn/4)
turn2=turn2+(turn/8)
turn1=turn1+(turn/16)if turn4<0then turn4=turn4+800if turn4>799then turn4=turn4-800if turn3<0then turn3=turn3+800if turn3>799then turn3=turn3-800if turn2<0then turn2=turn2+800if turn2>799then turn2=turn2-800if turn1<0then turn1=turn1+800if turn1>799then turn1=turn1-800call StretchBlt,mDC,0,200,800-int(turn4),100,gDC,int(turn4),int(sea4),800-int(turn4),100call StretchBlt,mDC,800-int(turn4),200,int(turn4),100,gDC,0,int(sea4),int(turn4),100call StretchBlt,mDC,0,280+wave3,800-int(turn3),120,gDC,int(turn3),int(sea3),800-int(turn3),100call StretchBlt,mDC,800-int(turn3),280+wave3,int(turn3),120,gDC,0,int(sea3),int(turn3),100call StretchBlt,mDC,0,380+wave2,800-int(turn2),150,gDC,int(turn2),int(sea2),800-int(turn2),100call StretchBlt,mDC,800-int(turn2),380+wave2,int(turn2),150,gDC,0,int(sea2),int(turn2),100call StretchBlt,mDC,0,480+wave1,800-int(turn1),150,gDC,int(turn1),int(sea1),800-int(turn1),100call StretchBlt,mDC,800-int(turn1),480+wave1,int(turn1),150,gDC,0,int(sea1),int(turn1),100' read the keyboard'escape/quitCallDLL#user32,"GetAsyncKeyState",_VK_ESCAPE ASlong,k1 ASlongif k1<0then[quit]'leftCallDLL#user32,"GetAsyncKeyState",_VK_LEFT ASlong,k1 ASlongif k1<0then[left]'rightCallDLL#user32,"GetAsyncKeyState",_VK_RIGHT ASlong,k1 ASlongif k1<0then[right]'upCallDLL#user32,"GetAsyncKeyState",_VK_UP ASlong,k1 ASlongif k1<0then[accelerate]'downCallDLL#user32,"GetAsyncKeyState",_VK_DOWN ASlong,k1 ASlongif k1<0then[brake]wait[movemouse]if MouseX<oldx then[left]if MouseX>oldx then[right]if MouseY<oldy then[accelerate]if MouseY>oldy then[brake]wait[left]selectcase turn
case-4
turn=-8case-2
turn=-4case-1
turn=-2case0
turn=-1case1
turn=0case2
turn=1case4
turn=2case8
turn=4endselect
oldx=MouseXwait[right]selectcase turn
case-8
turn=-4case-4
turn=-2case-2
turn=-1case-1
turn=0case0
turn=1case1
turn=2case2
turn=4case4
turn=8endselect
oldx=MouseXwait[accelerate]
speed=min(32,speed+1)
oldy=MouseYwait[brake]
speed=max(1,speed-1)
oldy=MouseYwait[quit]unloadbmp"ocean"unloadbmp"bmp"call ReleaseDC hwnd(#1), bDC
call DeleteDC mDC
call DeleteDC gDC
close#1close#m
end
Having fun with the blitter
What is it?
If you like graphics the blitter is going to be your best friend. Back in the early days graphics used to be written to and from a memory location in RAM by the processor. The PC used a videosync timer to poll the memory and paint the graphics to the screen. You had to code carefully to be sure your drawing was finished before the videosync kicked in.Blitting hardware was developed to allow large areas of graphics to be copied and pasted in memory without requiring processor cycles. This vastly improved animation and drawing that could be achieved between videosync events, It's kinda old fashioned, my Zx81 didn't have a blitter but my Amstrad and Commodore sure did.
Graphics have evolved still more and now its all "surfaces" and openGL rendering or DirectX rendering, but you can still have a lot of fun with the blitter.
Display Timing
How fast can we show blitted graphics? Notice I said "show", a moden PC's graphics card typically renders screen images at 60hz, or a frame every 16.6ms. Now the blitter can blit much much faster than this but there is little point in blitting for blittings sake if the graphic image is only shown once every 16.6ms.So its best to use the Liberty BASIC TIMER statement to control a drawing loop to slow things down and not waste time drawing graphics to the buffer that will never be seen.
While the TIMER uses millisecond values you will find that 16.6ms is the smallest value it can discern. Run the code below and see how the TIMER value jumps in 16.6ms increments. This is because Windows updates it's clock at 60hz.
What is the lesson? Don't try and run your TIMER loop faster than 17ms, the TIMER statement won't run any faster and even if it did you will be drawing graphics to the buffer that will never be seen.
In actual fact animation will run perfectly well at lower speed. The speed of your processor and graphics card will determine what is achievable but stay in tune with your PC, anything between 17ms and 100ms provide watchable animation.
Blitter Functionality
Blitting swaps one area of graphic for another. The blitter can merge, overwrite or transparently combine the graphics. It can also flip, mirror, stretch or shrink the image as it does so. Animation is achieved by moving one image relative to the other.Blitting is usually associated with animation or graphics transformations though it is equally happy stretching, blending, flipping or compressing static images.
Blitter code
You will need to cut and paste these blitter functions to the foot of each of the following code examples.How fast is the Blitter?
Lets run some unrestricted code on your machine to see what blitting performance you get. The code will create a copy of your screen, select the current screen image into that and then blit it back to the screen repeatedly. It is important to remember that the blitter is not blitting directly to the screen that you see in front of you but to the memory that defines that screen. Lets call it the screen buffer. The blitting will be very fast, you won't see anything but it is happening. Run it now.Fantastic result? unbelievably fast? you bet. On my machine which is a middle of the road laptop I rendered the half megabyte of graphics in 420 milliseconds, that's over 2300 frames per second, wow!
Now of course you didn't see anything. First because it was a black image blitted over a black image but more importantly the screen buffer was rendered to the LCD or Glass display much more slowly at 60 frames per second, very much slower indeed. So even if the image was changing you would only see 38 of those 2300 frames we rendered to memory.
That's great news, it means that if we need to, we can blit in massive amounts of graphics between video display updates. But neither should we let the blitter run unchecked.
Device Contexts
The blitter is made available to us via API calls from Liberty BASIC. Obviously before we can blit anything we need somewhere to blit from and somewhere to blit to. Typically we would blit to the screen buffer. So how do we access this buffer?We need to find it's address and would use an API call to do so.
This obtains a ulong number which is the pointer to the Device Context (DC) that Windows created for our graphicbox. So the screen buffer is in fact the DC that Windows has created behind the scenes.
Now we create a copy of that DC.
Then we fill it with some graphics by getting a handle to our "bmp" image and selecting it into the new DC.
And that's all we need to start blitting! DCs are amorphous things, if you select in a large bmp you can have lots of graphics in one location to blit from.
Once you have finished with a DC you must release and delete it.
That's a whistle stop tour of DCs, check the following link for help on API and DCs under the GDI banner.
http://lbpe.wikispaces.com/GDI
Basic Blitting
Lets draw ourselves some graphics to blit from. We use the left 225 pixels of the screen to draw some stars and then replicate that in the next 225 pixels giving us a 450 pixel image that will scroll without an obvious end. Confused? Don't worry the tutorial is about blitting not game graphics (That might be next).Then I draw a little circle in the remaining 50 pixels. Use the debugger to step through and see what's happening. Now when I start blitting I take the first 225 pixels from the memory DC and blit them to the buffer DC but stretch them width ways up to 500 pixels. In this way you cut out little parts of the memory DC and fill the buffer DC. Next iteration I move the source 5 pixels to the right in the graphics DC and so repetitively move the star background to the left.
Then I cut out the little circle and blit it a couple of times same size into the buffer. Net result a scrolling background with two sprites moving about. Well they would be sprites if I had used TransparentBlt, notice that the circle backgrounds overwrite when they cross over. If I had used TransparentBlt you would just see the white circles.
So there are several call you can make BitBlt, StretchBlt PrlBlt and TransparentBlt each has it's own strength some can flip and mirror some can't, one can do it transparently, basically pick your blitter call by the functionality you wish.
These API calls are well documented elsewhere so I'm not covering the detail here.
Double Buffering
Double buffering is something you will eventually come to. When you render large or complex image manipulations or start to render text you will find that the image starts to flicker. Text drawing in particular needs a double buffer. What happens here is that we draw all the complex graphics to an intermediate DC and then on a regular basis blit the contents to the buffer DC and leave it alone. This ensures that the buffer DC has a stable copy of what we want displayed and ensures rock solid graphics.The flicker is rooted in how Windows updates it's screen, the videosync is not made available to us as many windows may wish redrawn or updated. So the screen refresh happens randomly as far as we are concerned. Right in the middle of drawing a line or rendering some text. But because we can render a single screen image so quickly with the blitter it rarely, if ever, is half drawn when the refresh occurs.
One of the other great things about blitting is that you do not use up graphics memory, each DC consumes a finite amount of memory but after that you are just blitting between them and no other graphics memory is consumed.
Adding Text
We use another API call to render text to our DC. This action in itself triggers the need for the double buffer. Here we create the same moving background but ignore the circle sprite and instead render two sets of text. The text API calls firstly set the text background color to be transparent. Then they set the text color and render the text.Seasick?
Ok scrolling stars and text gets a bit boring. Do you get seasick? Check out this rolling ocean. Use the mouse or arrow keys to turn and speed up or slow down. It kinda gives the impression of being on a rolling ocean swell. The ocean.bmp has four strips for the horizon which I blit into the top of the picture depending on what the heading is. Then I blit four copies of the sea section. I roll these up and down using a sine curve and I also roll them forward to create more movement. The further away section rolls least and they double up their movement the nearer the bottom of the screen they are.Its just like having multiple backgrounds in the sprite engine. There is lots of time to draw sprites on top of this moving background. Doing so creates a pretty cool game environment.
Copy this bmp to your own PC
Where Now?
Actually the only barrier is your imagination.Happy coding
rodbird@hotmail.com
RETURN to the SUBMIT ARTICLE Page