Scrolling in text mode actually works the same as in graphic mode. The visible portion of video RAM is shifted using the starting address (Linear Starting Address). The one noticeable difference is scrolling is very jittery because shifting always occurs by entire characters in text mode whereas you can shift by individual pixels in graphic mode . This is due to the structure of video RAM in text mode. Data for
PCPCunderground
You can find SQUEEZE.PAS on the companion CD-ROM
individual pixels no longer exists here, so the Linear Starting Address also refers to whole characters and enables only very coarse scrolling.
To prevent this, we can use a few new VGA registers: The horizontal and vertical panning registers. In
panning, we're shifting the image contents by one pixel, which these registers allow in text mode as well.
For smooth scrolling in text mode, simply shift the image contents by panning in the desired direction. Once you have moved ahead by one character, set the corresponding panning register back to its initial value and modify the Linear Starting Address Register. This is necessary because the maximum panning distance is one character width or one character height. Panning therefore is only a fine control, while coarse scrolling is achieved through the Linear Starting Address.
Vertical panning is generated by CRTC-Register 8 (Initial Row Address), where Bits 4-0 indicate the graphic line where display of the first scan line begins. Incrementing this value by 1 makes Line 1 begin inside the character set the screen contents therefore move one line up.
Horizontal panning on the other hand, is the responsibility of the Attribute Controller. Here Register 13h (Horizontal Pixel Panning) provides for smooth motion in the x- direction. The table on the right shows the somewhat unusual value assignments for this register:
The necessary format conversion is done by a simple calculation (without requiring difficult IF-structures):
register-value := (panning-value - 1) mod 9
The complete demonstration program SCROLLT.PAS: Uses ModeXLib,Crt;
Var x, {x-position in pixels} x_dir, {x-direction}
y, {y-position in pixels} y_dir:Word; {y-direction}
Procedure Wait_In_Display;assembler;
{Counterpart to Wait_In_Retrace, waits for display via cathode ray} asm
mov dx,3dah {Input Status 1} @wait2:
in al,dx test al,8h
jnz @wait2 {Display on ? -> then finished} End;
Procedure Wait_In_Retrace;assembler;
{waits for retrace, also resets the ATC flip-flop by read access to Input Status 1} asm
mov dx,3dah {Input Status 1} @wait1:
in al,dx test al,8h
jz @wait1 {Retrace active ? -> then finished} End;
Value Function
0 Panning by one pixel 1 to 7 Panning by 2 to 8 pixels 8 No panning
PC
PCunderground
You can find SCROLLT.PAS on the companion CD-ROM
Procedure FillScreen;
{Fills video RAM with test image 160*50 characters in size} var i:word;
Begin
For i:=0 to 160*50 do Begin {character loop}
If i mod 10 <> 0 Then {write column counter ?} mem[$b800:i shl 1]:= {no, then '-'}
Ord('-') Else
mem[$b800:i shl 1]:= {yes, then column number in tens} ((i mod 160) div 10) mod 10 + Ord('0');
If i mod 160 = 0 Then {column 0 ? -> write row counter} mem[$b800:i shl 1]:=(i div 160) mod 10 + Ord('0');
End; End;
Procedure V_Pan(n:Byte);assembler; {performs vertical panning} asm
mov dx,3d4h {CRTC Register 8 (Initial Row Adress)} mov al,8
mov ah,n {set panning width} out dx,ax
End;
Procedure H_Pan(n:Byte);assembler; {performs horizontal panning} asm
mov dx,3c0h {ATC Index/Data Port}
mov al,13h or 32d {Register 13h (Horizontal Pixel Panning)} out dx,al {select; Bit 5 (Palette RAM Address Source)} mov al,n {set, in order not to switch off screen} or al,32d {write panning value}
out dx,al End;
Begin
TextMode(3); {set BIOS mode 3 (80*25 characters, Color)} FillScreen; {build test picture}
portw[$3d4]:=$5013; {double virtual screen width(160 character)} x:=0; {initialize coordinates and directions} x_dir:=1;
y:=0; y_dir:=1; Repeat
Inc(x,x_dir); {movement in x and y-directions} Inc(y,y_dir);
If (x<=0) or (x>=80*9) {turn around at borders} Then x_dir:=-x_dir;
if (y<=0) or (y>=25*16) Then y_dir:=-y_dir;
Wait_in_Display; {wait until display running}
SetStart((y div 16 *160) {set start address (rough scrolling} + x div 9);
Wait_in_Retrace; {wait until retrace active}
V_Pan(y mod 16); {vertical panning (fine scrolling)} H_Pan((x-1) mod 9); {horizontal panning (fine scrolling)} Until KeyPressed; {wait for key}
TextMode(3); {and set normal video mode} End.
After setting text mode and drawing a test image, the program switches over to double-virtual-width by setting Row-Offset Register 13h of the CRTC to the value 320 bytes/4 bytes = 80 (4 due to doubleword- access). The motion itself corresponds exactly - with somewhat different values - to the same procedure in graphic mode.
One special feature is dividing WaitRetrace into two parts: Wait_in_Display and Wait_in_Retrace. This is because the registers used here are linked to different timing circuits. Linear Starting Address is loaded by VGA directly after entering into the retrace, so a switch during the retrace will usually show no effect in the next image. This was previously unimportant because the entire operation was simply delayed by one retrace. Now we also have the panning registers, whose effects become immediate after being changed.
The starting address is, therefore, set during image construction, because it has no function at this point anyway, and is guaranteed to be set correctly for constructing the next image. The panning registers however are set during the retrace because their effects are immediate and must therefore take place in the invisible area of the screen.
The procedure Wait_in_Retrace has a second purpose. Accesses to the ATC are somewhat unconventional: This port alternates between index and data register with each write-access to Port-Address 3c0h. When the program starts the current status of this register is unknown. However, reading Input-Status Register 1, as happens in this procedure, switches Port 3c0h to the index function and thereby gives it a defined status. Since writing to the ATC (Attribute Controller) occurs almost immediately after the wait for the retrace, you can assume the index mode is active. The only possibility of this port switching over unnoticed is if a TSR "stumbles in" through an interrupt. This is highly unlikely however, and can be prevented anyway by a simple CLI instruction.
It's very important when writing to the ATC-index to always set Bit 5 at the same time. This bit controls access to the internal palette of the ATC. When Bit 5 is cleared the CPU obtains full access and the ATC disables itself; leading in the best case (if you reset the bit quickly enough) to flickering, but in the normal case to a complete system crash, where only a reboot will help.
Of course, this scrolling can also be combined with a split-screen. The procedure is exactly the same as in graphic mode. The split-screen is fully mode-independent, because you are directly programming the physical scan-line where the split occurs.