
                             VGA-Kurs - Part #3

"T.C.P.'s Beginner's Guide To VGA Coding"(TM) ist wieder da mit Teil III!
Zu Beginn als Appetithppchen eine erneut schnellere PutPixel-Routine. Die ist
jetzt meiner Meinung nach nicht schneller zu machen (hchstens in 386er Code).
Wer anderer Meinung ist, soll seine Lsung hier prsentieren.
Wichtig bei dieser Prozedur ist, das stets ein GLOBALE Konstante
'VGA : word = $A000;' deklariert wird.

const VGA : word = $A000;
procedure PutPixel(x,y:word;c:byte);assembler;
asm
  mov     es,VGA
  mov     di,x
  mov     dx,y
  mov     bx,dx
  shl     dx,8
  shl     bx,6
  add     dx,bx
  add     di,dx
  mov     al,c
  stosb
end;

Auerdem noch unentbehrlich: Eine GetPixel-Funktion. Diese ermittelt den
Farbwert des Pixels, der an den angegebenen Koordinaten steht.

function GetPixel(x,y:word) : byte;assembler;
asm
  mov     es,vga
  mov     di,x
  mov     dx,y
  mov     bx,dx
  shl     dx,8
  shl     bx,6
  add     dx,bx
  add     di,dx
  mov     al,es:[di]
  mov     [bp-1],al
end;

Diese Funktion verfhrt bei der Berechnung des Offsets des Pixels haargenau
so wie die Schwester-Prozedur. Nur am Schlu gibt es eine kleine nderung.
Statt den Pixel zu schreiben wird der Farbwert, der bekanntlich an der
Adresse A000h:Y*320+X steht, in AL ausgelesen und dann als Funktionsergebnis
zurckgeliefert.
Nun wre es vielleicht an der Zeit, da sich der geneigte Leser eine Unit
mit den besprochenen Routinen zusammenstellt, um die Beispiele einfacher
kompilieren zu knnen und eigene Programme schneller zu erstellen.
Aber nun zur Sache: In dieser Ausgabe werden wir uns einem hufig besprochenen
und geheimnisumwitterten Thema widmen: Dem Scrolly.
Was ein Scrolly ist, wei wahrscheinlich jeder. Eine Zeichenkette wird zum
angenehmeren Lesen von rechts nach links ge(sc)rollt. Dabei wird zuerst ein
Teil der Laufschrift am rechten Rand des Screens gezeigt, dann ein Stck nach
links bewegt und schlielich der nchste Teil angefgt. Einfaches Prinzip,
groe Wirkung. Um wirklich zu beeindrucken, sollte der Scrolly aber nicht nur
einfach scrollen sondern z.B. noch ein Plasma im Hintergrund bewegt werden
oder hnliches.
Beginnen wir im Praxisteil mit einem einfachen Textmode-Scrolly. Der Text wird
in der Konstanten Text abgelegt. Nun wird innerhalb der Schleife zuerst der
Buchstabe des Textes auf den Screen geschrieben (Zeile 13), dessen Nummer in
der Variablen Charno steht. Nun wird die komplette Zeile ab dem 2. Zeichen um
1 Zeichen nach links verschoben. Dazu folgendes: Wir haben in der ersten
Ausgabe gelernt wie der Bildschirmspeicher ab Adresse A000h organisiert ist.
Zur Erinnerung: Die Pixelinformationen sind hintereinander in einem 64000 Byte
groen Bereich abgelegt. Die Offset-Adresse lt sich nach der Formel
Offset = Y-Koord * 320 + X-Koord berechnen. Nun, im Textmodus ist dies so
hnlich. Der Bildschirmspeicher liegt an der Adresse B800h (B000h bei
Hercules), und ist 4000 Byte gro. Bei Segment B800, Offset 0 liegt also der
ASCII-Code des ersten Zeichens auf dem Bildschirm. Steht nun auf dem Screen
bei den Koords (0,0) ein "A", so findet man bei Adresse $B800:0 den Wert 65,
also den ASCII-Code von "A". An Adresse $B800:1 liegt nun aber nicht, wie zu
vermuten wre, der ASCII-Code des zweiten Zeichens, Koords (1,0), sondern das
sog. Attributbyte des ersten Zeichens. Dieses beeinhaltet die Farbe und die
Art des Zeichens, z.B. rot und blinkend. Um nun dem "A" die Farbe Rot und das
Attribut "blinkend" zuzuweisen, rechnet man den Wert fr die Farbe Rot (4) und
fr "blinkend" (128) zusammen, erhlt 132, und schreibt diesen Wert an die
Adresse $B800:1. Unter Pascal kann man zum ndern von Textfarbe und Attribut
auch die Prozedur "Textcolor" der Unit Crt benutzen. Rot und blinkend stellt
man z.B. ein mit "Textcolor(red+blink);".
Die Formel zum Berechnen der Adresse eines Zeichens ist also:
Adresse = (Y-Koord * 80 + X-Koord) * 2.
Zurck zu unserem Scrolly. Mit der Standard-Prozedur "Move" werden nun ab der
Adresse $B800:1922 158 Bytes (79 Zeichen) nach $B800:1920 (also um ein Zeichen
nach links) kopiert. Nun wird nur noch die Nummer des aktuellen Buchstabens
erhht und berprft, ob die Zeichenkette bereits am Ende ist, und wenn ja
wird Charno wieder auf 1 gesetzt. Dies geht solange weiter, bis eine Taste
gedrckt wird.
Achso: Generell ist es bei Scrollies natrlich wichtig, auf ein entsprechend
flssiges Scrolling zu achten. Dies besorgt die Prozedur "WaitRetrace", die
wir schon in der letzten Ausgabe kennengelernt haben. Ihr knnt das
"Waitretrace" ja mal aus der Schleife entfernen und durch ein "Delay(10)"
ersetzen, und zusehen, was fr ein Geruckel dabei herauskommt.
Augenbeleidigend!

program Scrolly1;
uses crt;
const Text : string = 'Hallo, dies ist ein Test Sc'+
                      'rolly, der sich solange wie'+
                      'derholt, bis ein Taste gedr'+
                      'ckt wird..................';
var Charno : byte;

procedure WaitRetrace;assembler;
asm
     mov     dx,3DAh
@l1: in      al,dx
     and     al,08h
     jz      @l1
@l2: in      al,dx
     and     al,08h
     jz      @l2
end;

begin
  Charno := 1;   { Erstes Zeichen des Scrolltextes }
  clrscr;                     { Bildschirm lschen }
  gotoxy(80,13); { Zum letzten Zeichen in Zeile 13 }
  repeat
    WaitRetrace;
    write(Text[Charno]);       { Zeichen schreiben }
    move(mem[$B800:1922],mem[$B800:1920],158);
    { 79 Zeichen um 1 Zeichen nach links schieben }
    inc(Charno);                { Nchstes Zeichen }
    if Charno > length(Text) then Charno := 1;
   { Wenn Zeichenkette am Ende, von vorne beginnen }
    gotoxy(80,13);      { Wieder zum Ausgangspunkt }
  until keypressed;
  readkey;
end.

Mal wieder eine Anmerkung: Der GotoXY-Befehl benutzt, wahrscheinlich um den
Umgang mit der Prozedur fr Anfnger zu erleichtern, ein Koordinatensystem,
das bei (1,1) beginnt. Wenn ihr also zu (20,20) wollt, mt ihr GotoXY(21,21)
eingeben. Dies ist oft sehr verwirrend, und man kann leider nur Abhilfe
schaffen, wenn man sich eine eigene GotoXY-Variante schreibt.
So, das waren erstmal die Grundlagen. Aber was wir wollten, war ja ein Scrolly
im VGA-Modus. Doch hier ist das Ganze nicht mehr so einfach. Als erstes
bentigen wir einen Font, denn sonst haben wir ja nichts zum Scrollen. Ich
gehe mal davon aus, da keiner von euch gewillt ist, sich jetzt einen eigenen
Font zu zeichnen, also mssen wir uns einen aus dem BIOS-Rom holen und so
einrichten, da wir frei auf ihn zugreifen knnen.

var FontSeg,FontOfs : word;
procedure GetFont;assembler;
asm
  mov     ax,1130h
  mov     bh,3          { Font-Nummer fr 8x8-Font }
  int     10h
  mov     FontSeg,es
  { Segment, in dem der Font abgelegt ist }
  mov     FontOfs,bp
  { Offset des Fonts }
end;

Rufen wir diese Prozedur auf, haben wir an Adresse FontSeg:FontOfs den 8 mal 8
Pixel-BIOS-Font. Ein Buchstabe ist also 8 Byte gro. Im ersten Byte stehen in
den einzelnen Bits die Informationen der ersten Zeile des Buchstabens, im
zweiten Byte die Bits der zweiten Reihe usw. Um nun festzustellen, ob im Font
ein Pixel gesetzt (Bit=1) ist oder nicht (Bit=0), mu man die Bits mit den
entsprechenden Potenzen von 2 und-verknpfen.
Im folgenden VGA-Modus-Scrolly wird also zuerst die erste Spalte des ersten
Buchstabens angezeigt, auf den Retrace gewartet, dann die komplette Zeile um
ein Pixel nach links verschoben und die nchste Spalte auf den Screen
geschrieben.
Die Nummer des Buchstabens steht wieder in Charno, die Spalte in Charpos.
In Character wird der ASCII-Code des aktuellen Zeichens abgelegt.

program Scrolly2;
uses crt;
const VGA = $A000;
      Bits : array[0..7] of byte =
               (128,64,32,16,8,4,2,1);
      Text : string = 'Ein Scrolly im VGA-Modus 13'+
                      'h, er scrollt und scrollt u'+
                      'nd scrollt.................';
var FontSeg,FontOfs : word;

{ Hier die Prozeduren GetFont und WaitRetrace
  einsetzen }

procedure Scroll;
var I,J : word;
    CharPos,CharNo,Color,Character : byte;
begin
  CharNo := 1;                   { Anfangsposition }
  repeat
    Character := ord(Text[CharNo]);
    { ASCII-Code holen }
    for CharPos := 0 to 7 do begin  { 8x8 Pixel je }
      for I := 0 to 7 do begin      { Zeichen      }
        if mem[FontSeg:FontOfs+(Character*8)+I] and
           Bits[CharPos] <> 0 then Color := 31
        else Color := black;
        { Wenn da entsprechende Bit gesetzt ist,  }
        { dann Farbe wei (31) setzen, andernfalls }
        { schwarz.                                 }
        mem[$A000:((100+I)*320)+319] := Color;
        { Pixel setzen }
      end;
      WaitRetrace;
      for J := 0 to 7 do for I := 0 to 318 do
        mem[$A000:((100+J)*320)+I] :=
          mem[$A000:((100+J)*320)+1+I];
      { Alles um einen Pixel nach links bewegen }
    end;
    inc(CharNo);
    if CharNo > length(Text) then CharNo := 1;
  until keypressed;
  readkey;
end;

begin
  GetFont;
  asm mov ax,13h; int 10h end;    { VGA-Modus 13h }
  Scroll;
  asm mov ax,03h; int 10h end;        { Textmodus }
end.

Dieser Scrolly wrde wahrscheinlich in einem Demo sehr wenig Anklang finden,
da der Font nicht gerade zu den allerhbschesten zhlt. Auerdem ist er blo
einfarbig. Letzteres Problem lt sich allerdings sehr leicht aus der Welt
schaffen.
Deklariert einfach ein Array of byte mit den gewnschten Farben der Zeilen.
Zum Beispiel:

const Colors : array[0..7] of byte = (25,27,29,31,31,29,27,25);

Nun ersetzt ihr im Listing die Zeile "Bits[CharPos] <> 0 then Color := 31"
durch "Bits[CharPos] <> 0 then Color := Colors[I]". Dadurch wird jeder Zeile
des Scrollies ein der Reihe nach der entsprechende Farbwert aus dem
Colors-Array zugewiesen. In diesem Fall beeinhaltet das Array einen
Grau-Verlauf. brigens stellen in der Standard-Palette (s. Teil II) die Farben
Nummer 16 bis 31 einen Grau-Verlauf bereit (16=Schwarz, 31=Wei), den man bei
Bedarf nutzen sollte.
Wer das Beispiel aufmerksam studiert hat, dem wird es auch nicht schwerfallen,
einen eigenen Font einzubauen.
So viel zu horizontalen Scrollies. Natrlich gibt es auch vertikale Scrollies
(eines der besten Beispiele ist am Ende von 2nd Reality zu bestaunen).
Diese sind im Modus 13h zwar ruckelfrei realisierbar, doch durch das Fehlen
weiterer Bildschirmseiten (wie im Mode-X, s.u.) wird dies ein sinnloses
Unterfangen, denn es kann maximal eine Seite gescrollt werden, dann ist das
Ende der Fahnenstange erreicht.
Hier jedoch trotzdem ein solcher Scrolly im Mode 13h, nur um das Prinzip zu
verdeutlichen:

program VertScrolly;
uses crt;
const VGA : word = $A000;
var i,j : word;
    c   : char;

{ Hier Prozedur WaitRetrace einfgen }

procedure SetStart(Adresse:word);assembler;
asm
  mov     dx,3D4h             { CRTC-Indexregister }
  mov     al,0Ch
  mov     ah,byte ptr Adresse + 1
  out     dx,ax
  mov     al,0Dh
  mov     ah,byte ptr Adresse
  out     dx,ax
end;

begin
  randomize;          { "Zufalls"zahlen generieren }
  asm mov ax,13h; int 10h end;     { VGA-Modus 13h }
  for i := 0 to 65535 do mem[vga:i] := random(256);
  { Bildschirm fllen }
  i := 0;
  j := 80;
  repeat
    if keypressed then begin
      c := readkey;
      if c = ' ' then j := -j  { Richtung umkehren }
      else exit;
    end;
    delay(10);             { Ohne Delay zu schnell }
    waitretrace;
    SetStart(i);        { Neue Startadresse setzen }
    inc(i,j);          { Zhler erhhen/vermindern }
  until keypressed;
  readkey;
  asm mov ax,03h; int 10h end;        { Textmodus }
end.

Die Prozedur SetStart bildet den Kern dieses Programms. Sie stellt mittels des
CRTC ein, an welcher Adresse der Bilschirm 'beginnt'. Dabei wird kein einziges
Byte kopiert oder bewegt, sondern blo die Adresse, ab der die VGA die im
Bildschirmspeicher abgelegten Informationen auf den Monitor bringt, verndert.
Ruft man sie also mit

SetStart(32000);

auf, so wird der Bildschirminhalt erst ab dem Offset 32000 dargestellt. In
diesem Modus gibt es aber wie gesagt den Nebeneffekt, da hier in der zweiten
Hlfte des Bildschirms der bersprungene Teil wieder angefgt wird. 
Einen anderen Bereich der Scrollies, das Full-Screen-Scrolling, werden wir
spter behandeln, wenn wir zum Thema "Mode-X" kommen. Der Modus 13h hat nmlich
das Manko, da er nur eine Bildschirmseite untersttzt und so den Speicher der
VGA-Karten (heutzutage mind. 512KB) kaum ausnutzt. Deshalb ntzt das Scrolling
des gesamten Bildschirminhalt mittels des CRTC (Cathode Ray Tube Controller)
wenig, da immer nur derselbe Bildschirminhalt gescrollt werden kann. Im Mode-X
(oder Chain-4) dagegen hat man bis zu 4 Virtuelle Bildschirme zur Verfgung.
Aber das besprechen wir noch ausfhrlich in einem der nchsten Teile.
Also, das war's zum Thema Scrollies, Thema von Part IV wahrscheinlich:
Sprites.





[ This text copyright (c) 1995-96 Johannes Spohr. All rights reserved. ]
[ Distributed exclusively through PC-Heimwerker, Verlag Thomas Eberle. ]
[                                                                      ]
[ No  part   of  this   document  may  be   reproduced,   transmitted, ]
[ transcribed,  stored in a  retrieval system,  or translated into any ]
[ human or computer language, in any form or by any means; electronic, ]
[ mechanical,  magnetic,  optical,   chemical,  manual  or  otherwise, ]
[ without the expressed written permission of the author.              ]
[                                                                      ]
[ The information  contained in this text  is believed  to be correct. ]
[ The text is subject to change  without notice and does not represent ]
[ a commitment on the part of the author.                              ]
[ The author does not make a  warranty of any kind with regard to this ]
[ material, including,  but not limited to,  the implied warranties of ]
[ merchantability  and fitness  for a particular  purpose.  The author ]
[ shall not be liable for errors contained herein or for incidental or ]
[ consequential damages in connection with the furnishing, performance ]
[ or use of this material.                                             ]
