Von Flix of Delta Force
Sie waren bestens gehütet und nur der Elite der Szene bekannt. Jetzt gibt die Delta-Force ihre Geheimnisse der Grafik- und Soundprogrammierung auf dem Atari ST preis. In diesem Teil erfahren Sie alles über die Technik der Fullscreen-Programmierung, die die Auflösung des ST beinahe verdoppelt.
Willkommen zum ersten Teil einer Serie, in der wir -die »Delta Force« - Ihnen die Programmierung von sogenannten »Demos« etwas näherbringen möchten. Demos sind Programme, die die Hardware des Atari ST über seine Grenzen hinaus nützen. Demo-Programmierer stellten als erste auf dem ST mehr als 16 Farben gleichzeitig dar, entwickelten die mit Abstand schnellsten Kompressions-Routinen, programmierten Vektorgrafik-Routinen, die selbst »Starglider II«-Fans erblassen lassen und schufen Sound-Routinen, die mit extrem wenig Rechenzeit den Sound des Amiga auf jedem ST (nicht STE!) reproduzieren. Es waren Demoprogrammierer, die die schnellsten Apfelmännchen-Programme auf dem ST schrieben, die dem ST ein Hardwarescrolling gaben und die, wie in dieser Folge erklärt, einem ST zur einer Auflösung von 416 mal 276 Punkten verhalfen - ohne Hardware versteht sich. Zum Verständnis der Artikel sind Grundkenntnisse in Assembler und der Hardware des ST hilfreich. Die geplanten Themen sind: Beseitigung aller Bildschirmränder per Software, Hardware-Scrolling auf dem ST (»Sync-Scrolling«), Musik mit dem Soundchip und die Programmierung von »Neochrome Master«.
Zu Anfang möchte ich Ihnen die verschiedenen Mitglieder der »Delta Force« vorstellen. Die vier Ältesten der Gruppe sind »New Mode« (Programmierer), »Slime« (Grafiker), »Questlord« (Grafiker) und »Chaos, Inc.« (Programmierer). Später kamen dann »Ray« (Programmierer), »Big Alec« (Musiker) und meine Wenigkeit »Flix« dazu. Die neuesten Crew-Mitglieder sind »Sammy Joe«, »Thor« (Grafiker) und »Mascot« (Mädchen).
Zurück zu den Bildschirmrändern, den sogenannten »Bordern«. Obwohl es früher kaum jemand für möglich hielt: Die Border des ST lassen sich softwaremäßig beseitigen. Demos, die im Bildrand Grafik darstellten, waren auch die Basis für die Entwicklung der bekannten Overscan-Hardware.
Der erste Mensch, der jemals auf dem ST irgendwelche Grafik in einem Border zeichnete, war Sven alias »Alyssa«. In einem von ihm geschriebenen Intro (Vorspann zu einem gecrackten Spiel), nutzte er den unteren Rand für Grafik. Das war 1987. Ohne diesen grundlegenden Trick wäre heute ein Fullscreen oder Sync-Scrolling nicht denkbar. Über den Verbleib von »Alyssa« herrscht bis heute Unklarheit.
»The Exceptions« (TEX) verwendeten Grafik im unteren Border erstmals in der B.I.G.-Demo. Zur »Öffnung« des unteren Borders schaltet man am Ende der 199. Zeile die Bildfrequenz für ein paar Taktzyklen auf 60 Hertz. Der Bildschirmspeicher wird dann ganz normal weitere 50 Zeilen ausgelesen!
Nachdem TEX diesen Trick aufgegriffen hatten, folgte bald die erstmalige Öffnung des oberen Borders. Die Umschaltung erfolgt hier 13 oder 29 Zeilen oberhalb des normalen Bildspeichers. Diese Zweideutigkeit geht aus den verschiedenen Versionen des MMU (Baustein zur Speicherverwaltung) hervor. Da die Stelle zum Öffnen des oberen Randes oberhalb des regulären Bildschirmbereichs liegt und der Timer B des MFP erst ab Bildbeginn zählt, griffen TEX zu einer sehr umständlichen Methode: Sie setzten einen Timer-B-Interrupt in der letzten Zeile und warteten dann so lange, bis der Rasterstrahl die richtige Stelle im oberen Rand erreicht hatte. Inzwischen weiß ich, wie es noch besser geht. Der Timer-B-Interrupt zählt zwar nur »echte« Bildschirmzeilen (200), jedoch gibt es noch den HBL-Interrupt, der jede Bildzeile mitzählt (313). Zu Beginn des VBLs läßt man den HBL bis zur richtigen Stelle zählen und schlägt dann zu. Ist der obere und untere Rand geöffnet, steigt die vertikale Auflösung auf 277 Zeilen in der niedrigen Auflösung.
Schon bald war der rechte Border an der Reihe. Leider ist dieser nicht mit irgendwelchen Timer-B oder HBL-Interrupts zu öffnen. Der ST stellt Grafik im rechten Border dar, wenn man in jede Zeile zu einem ganz bestimmten Zeitpunkt die Frequenz kurz auf 60 Hz schaltet. Diesen »ganz bestimmten Zeitpunkt« zu erreichen, erfordert eine ganz neue Programmiertechnik. Wenn man in einem gewöhnlichen Interrupt, etwa dem Timer B, VBL oder HBL, eine kleine Farbumschaltung einbaut, sieht man sofort, daß diese Interrupts nicht immer exakt an der gleichen Stelle innerhalb der Zeile auftreten. Die Farbumschaltung wackelt ständig nach links und rechts. Um Befehle, wie Farbumschaltungen oder unsere Frequenzumschaltungen exakt an der gleichen Stelle aufzurufen, muß man sie mit dem Rasterstrahl des Monitors synchronisieren. Eine geniale Methode, um diesen Effekt zu erreichen, ist folgende:
WAIT: MOVE.B $FF8209.W,D0 ; Screen-Low-Byte
BEQ.S WAIT ; soll nicht 0 sein
NOT.B D0 ; D0 negieren
LSL.B D0,D0 ; Synchronisation
Wenn man diese Routine bei jedem Bildaufbau ausführt, werden alle folgenden Befehle jeden VBL genau an der gleichen Stelle ausgeführt, das heißt die Farb- oder Frequenzumschaltungen sind stabil.
Aber was macht diese kleine Routine? Zunächst holt sie das Low-Byte der Bildschirmadresse nach DO. Dieses Byte gibt exakt die Position des Rasterstrahls innerhalb der Zeile an. Bei kleinen Werten befindet sich dieser weiter links, bei großen weiter rechts in der Zeile. Enthält das Low-Byte den Wert Null, befinden wir uns gerade im nicht sichtbaren Bildschirmbereich und müssen deshalb warten (BEQ.S WAIT). Nun wird dieses Byte negiert und der LSL-Befehl ausgeführt. Wie man jedem Prozessorbuch entnehmen kann, benötigt der LSL-Befehl genau 8+2*n Taktzyklen. Das bedeutet, daß der Befehl desto mehr Taktzyklen benötigt, je größer der Wert in DO ist. Egal an welcher Stelle der Bildschirmzeile der Prozessor gerade mit obiger Routine beschäftigt ist: Er ist immer am Ende des Zeile mit ihr fertig - die Synchronisation ist vollbracht.
Auf diese Technik bauen alle Fullscreen- und Sync-Scrolling-Routinen auf. Zur Synchron-Programmierung sollte man noch wissen, daß pro Bildaufbau (bei 50 Hz) 160000 Taktzyklen verstreichen. In einer Zeile verstreichen somit 512 Taktzyklen. Nun baut man an jener bestimmten Stelle die Bildschirmfrequenzumschaltung ein, und der Border öffnet sich. Natürlich benötigt das viel mehr Rechenzeit als die Öffnung des oberen oder unteren Borders, da die Umschaltung jeder Zeile erfolgen muß. Hat alles geklappt, stellt der ST nun 204 Bytes pro Zeile dar! Dies ergibt theoretisch statt 20 nun 25,5 Grafikworte pro Zeile. Von diesen benutzen wir aber nur 23, da sonst das Bild auf manchen STs verzerrt ist. Außerdem gibt es so gut wie keine Monitore, die einen so großen sichtbaren rechten Rand haben. Ein Demo-Screen mit geöffnetem oberen, unteren und rechten Rand gab es in der »Amiga-Demo« von TEX zu bewundern.
Da sich das gleiche Spielchen mit dem linken Rand nicht treiben ließ, hielt man es lange Zeit für unmöglich diesen zu öffnen. Doch mit dem Erscheinen der »Death Of The Left Border«-Demo der »TNT-CREW« wurden die Ungläubigen eines besseren belehrt. Zur Krönung gab es dann in der legendären Union-Demo noch einen Screen von »Level 16«, der erstmals auf dem ST alle Ränder öffnete: Der erste »Fullscreen«! Die Demo-Programmierer hatten wieder einmal das Unmögliche möglich gemacht. Doch wie funktioniert dieser Trick? Ganz einfach: Anstatt kurzzeitig auf 60 Hz zu schalten, nimmt man einfach 71 Hz - natürlich nur für ein paar Taktzyklen. Bei längeren Umschaltungen auf 71 Hz kann der Monitor Schäden davontragen (obwohl ich solche Schäden noch niemals gesehen habe). Wenn man jetzt den rechten Border zusätzlich öffnet, stellt der ST 230 anstatt 160 Bytes pro Zeile dar. Damit die Öffnung der seitlichen Ränder auch auf jedem ST läuft, sind sogenannte »Stabilisator-Umschaltungen« am Ende jeder Zeile notwendig. Dazu schalten Sie entweder kurz auf 71 Hz oder in die mittlere Auflösung. Im Grunde ist es egal, welche Methode man verwendet. Ich bevorzuge jedoch die 71-Hz-Umschaltung, die erfahrungsgemäß stabiler läuft. Der Stabilisator ist notwendig, da der Shifter am Ende des rechten Randes auf das letze Grafikwort wartet. Dieses Wort kommt aber niemals an, und um den Shifter nicht komplett zu verwirren, löst die 71-Hz-Umschaltung eine Art Shifter-Reset aus.
Wenn man nun alle Ränder auf einmal öffnet, hat der Bildschirmspeicher eine Größe von 160+230*276 =63640 Byte. Die erste Bildschirmzeile geht immer beim Synchronisieren drauf, deswegen ist dort die Darstellung auf 160 Byte begrenzt. Aber: Der Bildschirmspeicher ist jetzt fast doppelt so groß wie in der regulären niedrigen Auflösung!
Für die Umschaltungen der Frequenz benötigen wir zwei Register des Shifters:
$FFFP820A: 0: 60 Hertz 2: 50 Hertz
$FFFF8260: 0: Niedrige Aufl. 2: Hohe Aufl.(71 Hertz)
Um die verschiedenen Frequenzumschaltungen durchzuführen, gibt es verschiedene Varianten. Die Demo-Gruppe »Unlimited Matricks« (ULM) verwendet die »Immediate«-Adressierung. Eine Umschaltung auf 60 Hz sieht dann wie folgt aus:
MOVE.B #1,$FF820A.W ; 60 Hz (18 Taktzyklen)
MOVE.B #2,$FF820A.W ; 50 Hz (16 Takt Zyklen)
Abgesehen davon, daß diese Methode viel Rechenzeit verbraucht, soll sie auch nicht auf allen ST-Modellen laufen. Ein andere Methode verwendet zwar Daten-und Adreßregister, ist aber dafür auch schneller. Hier eine 60-Hz-Umschaltung, die nach diesem Prinzip arbeitet:
(D0=0, A0=$FF820A.W, D1=2, A1=$FF8260.W für 71 Hz)
MOVE.B D0,(A0) ; 60 Hz (8 Taktzyklen)
MOVE.B D1,(A0) ; 50 Hz (8 Taktzyklen)
Im Listing auf der TOS-Diskette sind die Register so geschickt genutzt, daß wir nur D0, A0 und A1 benötigen. Ein Datenregister benötigen wir zur Synchronisation. Hier machen wir uns die Tatsache zunutze, daß in DO immer nur eine Null stehen muß:
WAIT: MOVE.B $FF8209.W,B0 ; Low-Byte
BEQ.S WAIT ; soll nicht 0 sein
NOT.B D0 ; D0 negieren
LSR.B D0,D0 ; Synchronisation
Nach dem LSR-Befehl steht in D0 garantiert immer eine Null, die wir für die Borderroutine gleich weiterverwenden. Im anderen Datenregister DI muß eine 2 stehen. Dieses Datenregister ist überflüssig, da man sich den gewünschten Wert auch anderweitig beschaffen kann:
(D0=0, A0=$FF820A.w):
MOVE.B D0,(A0) ; 60 Hz
MOVE.W A0,(A0) ; 50 Hz
Diesen Optimierungsschritt zu verstehen ist nicht ganz einfach. Der zweite Befehl schreibt das Wort $820A nach $FF820A. Das zweite Byte des Worts ist bedeutungslos, da die Adresse $FF820B des Shifters unbenutzt ist. Der Shifter liest vom ersten Byte ($82) nur die unteren zwei Bit aus, berücksichtigt also nur den Wert $02. Da die Shifterregister ab Adresse $FFFF8200 liegen, sparen wir ein Daten register.
Im Sourcecode auf der Diskette befindet sich noch eine Unterroutine, die austestet, welches MMU-Modell vorliegt und wieviele Zeilen es bei geöffnetem oberen Rand darstellt (229 oder 213). Das Programm läuft so auf jedem ST, STE, Mega ST und Mega STE (8 MHz!), jedoch nicht auf einem TT. Es hat mich über einen Monat gekostet, die Routine auf allen möglichen ST-Chip-Konfigurationen auszutesten und austesten zu lassen (Thanx to Alien/ST-CNX).
Hier noch ein »Schmankerl« für erfahrene Fullscreen-Programmierer. Man kann den unteren Rand noch um zwei Zeilen verlängern, indem man am Ende des unteren Borders eine zweite 60-Hz-Umschaltung einbaut. Um eine noch größere Auflösung zu erzielen, hatte ich die Idee, einen Fullscreen in mittlerer Auflösung zu schreiben. Auf meinem ST gelang mir dies recht schnell. Später entdeckte ich, daß der Screen nur auf meinem ST lief! Dank New Mode lief die Routine zwei Monate später dann auf jedem ST. Das Schwierigste an der Fullscreen-Programmierung ist die Tatsache, daß man alle weiteren Routinen (Laufschriften etc.) in die Wartezeit der Borderroutine synchron »einfüllen« muß. Dazu benötigen Siegenaue Kenntnisse über die Anzahl der Taktzyklen pro Befehl. Hinweise hierzu finden Sie auch im Quelltext auf der TOS-Diskette. Natürlich haben wir das ausführbare Programm gleich mit dazu gepackt. (ah)