Virtual screen |
In this example I will show a virtual screen which contains graphical characters. This screen has 8x8 pixels cells and each cell can have a charachter. The screen resolution will be 40x25 chars, so it's exactly 320x200 pixels. A graphical character code is stored in 8bits, so the screen area uses 1000 bytes. For each code we define a box bitmap which has 8*8=64 bytes, so the graphic set needs 256*8*8 = 16384 bytes.
The following pictures shows a graphical charset. In this case we have only a few characters defined. The blue boxes are empty. The first yellow box represents a "space" character.
A (possible) game screen built with these chars is something like this: That's it. There is a quasi screen in the physical memory, which contains 40x25 graphic chars and this makes the exactly same dimensions as for the real screen. We need a 320x200 bytes memory buffer to build the screen. Becase this memory is not too big, and nowadays the CPUs are as fast as hell, we can regenerate the whole screen every time when we want to show something new. (Everything will be synchronized to the vertical retrace, but this comes later. Also later on we are going to show sprites on this virtual screen.)When the new phase of the screen is done (it's rendered), we simply move - copy over - each pixels from virtual screen to the physical screen.
Modify the gfx256 unix: vgfx256, the unit for our virtual screen
From the previous lessons we have several functions and procedures who work with the physical screen. Now, if we want to do the same things on the virtual screen, we have to change the destination memory area. For the physical screen we had the predefined variable SegA000 which points to the screen memory. Becase the virtual screen will have exactly the same structure as the physcial one, the only one difference is the destination address.
Remember, in each procedure we use a simple MOV ES,SegA000 instruction to address the screen, then the register DI points to the right pixels. The solution is obvious then. Instead of using the SegA000 variable, we're gonna use our own segment pointer that addresses our virtual screen. I call the modified unit vgfx256. This unit does have a new variable called ScreenSeg.
Pay attention! This variable will point to the segment A000 by default, so if you use the unit, there is no difference at all. BUT! If you allocate a new memory area and set the ScreenSeg to this area, the vgfx256 will work with that new memory, which is the virtual screen from then on. Clear?
There must be some other procedures to handle the virtual screen. They are quite simple, though.
procedure InitVirtualScreen; begin if vsptr=nil then getmem(vsptr,64000); ScreenSeg:=seg(vsptr^); end; procedure DoneVirtualScreen; begin if vsptr<>nil then freemem(vsptr,64000); end; procedure ShowVirtualScreen; assembler; asm push DS cld mov cx,32000 xor si,si xor di,di mov ES,SegA000 mov DS,ScreenSeg rep movsw pop DS end; |
The virtual character screen
This is the method how we draw the virtual character screen:
INIT pointers (charscreen, charbitmaps, virtual screen) LOOP for the rows (25) LOOP for the columns (40) GET graphchar code CALC graph bitmap address (ADDR:=64*CODE) SHOW one graphchar NEXT (advance pointers) END LOOP for the columns END LOOP for the rowsOf course, the procedure is written completely in assembly. Both the charscreen and the character bitmaps are stored in the pascal's data segment, therefore DS will point them. The virtual screen will be addressed by the ES:DI pointer. We use BX to access the charscreen, while SI is for reach the bitmaps.
This is the inner loop that shows one char. Note that we use the MOVSW to show 2 pixels.
{ 1 graphic char } mov cl,8 @cloop: movsw movsw movsw movsw { 8 pixels (4*2 pixels) } add di,320-8 { next line } dec cl jnz @cloopI hope that's clear. This small code shows one 8x8 pixels box - a.k.a graphic char - on the virtual screen. DS:SI points to the graphic char's source bitmap, while ES:DI points to the virtual screen.
The first outer loop is for drawing one character line. Here CH counts the characters, while BX points to the character array. ES:DI has the destination (virtual screen) address.
mov ch,40 @cline: { character calculation } xor ax,ax mov al,[bx] { get the charcode from the charscreen } inc bx { next char } shl ax,6 { CODE=CODE shl 6, so CODE=CODE*64 ! } mov si,chrsOffs { base address for the bitmaps } add si,ax { done, SI got the source address } { 1 graphic char, see above } add di,8-8*320 { position for the next char } dec ch jnz @clineThe other outer loop is for the rows. We have 25 character rows on the screen. To make life easier, I save the DI in the stack, therefore the address calculation for the next charline is a piece of cake.
mov dx,25 @sloop: push di { show a charline, see the code above ... } pop di add di,8*320 dec dx jnz @sloopNow put all together. The procedure takes two parameters.
procedure VirtualCharScreen(scrnOffs,chrsOffs:word); assembler; asm cld xor di,di mov ES,ScreenSeg mov bx,scrnOffs mov dx,25 @sloop: push di mov ch,40 @cline: { character calculation } xor ax,ax mov al,[bx] inc bx shl ax,6 mov si,chrsOffs add si,ax { 1 graphic char } mov cl,8 @cloop: movsw movsw movsw movsw add di,320-8 dec cl jnz @cloop add di,8-8*320 dec ch jnz @cline pop di add di,8*320 dec dx jnz @sloop end; |
Test
This is the screen that we are going to show. The test program has some new things. It should be clear though, it's just plain pascal. Check out the comments for the details.
program vtest1; uses vgfx256; var gfxset : array[0..16383] of byte; { the bitmap data - 8*8*256 bytes } vscr : array[0..24,0..39] of byte; { the character screen - 40*25 bytes } procedure LoadGfx(fname:string); { load the bitmap data from file } var f : file; begin assign(f,fname); reset(f,1); blockread(f,gfxset,16384); close(f); end; procedure floor(x,y,w:integer); { show floor - horizontal blue walls on the picture } var i : integer; begin for i:=0 to w-1 do vscr[y,x+i]:=2; end; procedure ladder(x,y,h:integer); { show ladder - the organge ladders on the picture } var i : integer; begin for i:=0 to h-1 do begin vscr[y+i,x]:=6; vscr[y+i,x+1]:=7; end; end; var i,j : integer; x,y : integer; begin graph320x200; InitVirtualScreen; { init the virtual screen } LoadGfx('mychar.dat'); { load the bitmaps for the chars } fillchar(vscr,1000,0); { clear the charscreen (spaces) } { ****************************** draw our game map ****************************** } for i:=0 to 39 do { red wall - horizontal frame lines } begin vscr[0,i]:=1; vscr[24,i]:=1; end; for i:=1 to 23 do { red wall - vertical frame lines } begin vscr[i,0]:=1; vscr[i,39]:=1; end; floor(2,5,8); floor(2,9,25); floor(5,14,25); floor(12,19,15); { blue floors } for i:=0 to 24 do { some random items only on the floor } for j:=0 to 39 do begin if (vscr[i,j]=2) and (random(100)>90) then vscr[i-1,j]:=3; if (vscr[i,j]=2) and (random(100)>80) then vscr[i-1,j]:=5; end; ladder(5,4,5); ladder(20,8,11); ladder(15,18,6); { ladders } { ******************************************************************************* } { show the characters on the virtual screen } VirtualcharScreen(ofs(vscr),ofs(gfxset)); { show the rendered screen } ShowVirtualScreen; readln; { free the allocated buffer } DoneVirtualScreen; text80x25; end. |
Downloads: [ Vgfx256 unit | vtest1 | Grpahic charset ]