Hallo Bit Byter,
warum einen Kurs über das geheimnisvolle Demoprogrammieren werden sich einige Mitglieder jetzt
fragen. Nun die kreativste Arbeit am XL ist das Programmieren (finde ich zumindest). Ich bin ein
leidenschaftlicher Demo-Sammler und finde es interessant, wie sich die Demos in den vergangenen
Jahren verändert haben. Auch ich erlag dem Trugschluß so was nicht programmieren zu können.
Ich dachte, irgendwie ist es ein wenig Zauberei und ich kann das nie. Auch benötigt man
Assembler-Kenntnisse, die über das Normale hinausgehen. So fing ich an im Jahre 1990 meine
ersten Grafik-Demos zu schreiben und zwar in Assembler.
Was sind Demos eigentlich?
Demos haben eigentlich keinen Sinn, sie sollen den Betrachter durch nie (?) dagewesene Effekte
beeindrucken. Sie sind eigentlich kleine Kunstwerke, wie Videoclips. Daneben kann der
Programmierer sein Können unter Beweiß stellen. Wir werden sehen, daß es meistens nichts mit
Können an sich zu tun hat, sondern einfach mit Cleverness.
Welche Arten von Demos kann man unterschieden?
Wie in jeder Szene gibts es gewisse Fachbegriffe, die für bestimmte Effekte benutzt werden. Doch
unterscheidet man auch die Arten der Demos, d.h. wie sind sie aufgebaut sind in ihrer
Grundstruktur. Hier folgt nun meine Einteilung der Demos, die nicht umbedingt vollständig ist:
-Screen: Mit Screen bezeichnet man einen kompletten Bildschirmaufbau mit allen Effekten.
Nach Beendigung durch den Betrachter, meistens per SHIFT oder START kehrt das
Demo wieder ins DOS zurück oder macht einen Warmstart.
Bsp. meistens die Titelbilder auf den Magazin-Disketten.
-Intro: Intro werden oft kurze Demos-Screens oder Demos genannt. Wie der Name schon sagt,
dienen sie als Einführung in den Hauptteil. Sie sind meistens nicht so aufwendig und
wurden meistens in kurzer Zeit geschrieben. Dienen oft nur zum Installieren des
Entpackers, der Laderoutine, der Ausgabe des Titels und einer Please wait
loading... -Mitteilung.
-Outro: Gegenteil von Intro. Viele Demoprogrammierer und Gruppen verwenden ein Outro. Es
ist als kleines Demo zum Schluß gedacht, vielleicht mit Credits (s.u.). Sonst gilt das
gleiche wie führ Intro. Danach wird entweder ein Kaltstart oder zumindest ein Reset
ausgeführt.
-Mainpart: Hauptteil. Oft eingekreist durch ein Intro und Outro. Aber häufig fehlt das Outro. Hier
werden alle Effekte gezeigt, die der Programmierer dem Betrachter offenbaren möchte.
-Mega-Demo: Viele Demogruppen schreiben nicht nur einen Effekt. Und da der Hauptspeicher
des Computers begrenzt ist, lagert man die Effekte auf Diskette aus. Fast immer
wird mindestens eine Diskettenseite belegt. Solche Demos müssen gebootet
werden. Ein Mega-Demo besteht aus mehreren Screens oder Parts. Nach dem
Beenden durch den Betrachter wird der nächste Teil der Demo von Diskette
geladen.
Bei den 16-Bittern hat eine Mega-Demo meistens ein Menu, durch welches man
die verschiedenen Parts auswählen kann. Kommt aber bei XL-Demos sehr selten
vor. Die Reihenfolge der einzelnen Teildemos ist meistens vorgegeben.
-Menü: Es gibt verschieden Arten von Menüs. Die ersten Menüs waren aufgebaut wie das Dos
2.5 Menü. Durch A wurde Part 1 geladen, durch B Part 2 usw.
Solche Menüs sind praktisch, aber langweilig. Die Demogruppen wollten den Betrachter
mehr in die Demo einbeziehen und mehr Spaß vermitteln. So kamen die Game -Menüs
auf. Per Joystick oder Cursortasten steuert der Betrachter wie in einem Videospiel eine
Figur durch ein Labyrinth und führt es zu Türen oder Höhlen, hinter denen sich die
einzelnen Demos befinden. Somit waren der Gestaltung von Menüs keine Grenzen mehr
gesetzt. Ein solches Menü habe ich aber bis heute nicht auf dem XL gesehen!
-Multipart: Multiparts kamen auf den 16-Bittern (Amiga, Atari ST) in Mode. Multipart heißt ja
eigentlich übersetzt, daß das Demo mehrere Effekte bzw. Screens hat. Was unterscheidet
dann ein Multipart-Demo von einer Mega-Demo? Meistens ist ein Multipart keine
Bootdiskette, sondern ein einzelnes File, das entweder vom Dos aus oder von einem
Gamedos-Lader geladen wird. Ein Multipart ist also ein File, das mehrere Effekte
enthält, die nacheinander gezeigt werden. Sie stehen komplett im Speicher und in der
Regel wird nichts mehr nachgeladen. Auch hat der Betrachter oft keine Möglichkeit zum
nächsten Effekt zu springen , sei es durch einen beherzten Druck auf Space, Shift oder
Start. Multiparts sind auf jedenfall schwerer zu programmieren, da ja der komplette
Speicher als Grenze dient, der Programmcode länger ist und damit mehr Speicher
benötigt. Der Programmierer muß genau wissen, wo er welchen Effekt im Speicher hat.
Je mehr Effekte er unterbringen kann, umso effektiver hat er den Speicher genutzt und
letztendlich abwechslungsreicher ist es.
Ein Grund für das Aufkommen von Multiparts könnte gewesen sein, daß die Computer
vermehrt mit Festplatte ausgestattet sind und wie soll man eine Bootdiskette (egal ob
Amiga, ST oder PC), die noch für das jeweilige Dos unleserlich ist (wie auf dem XL,ST,
Amiga), auf Festplatte installieren bzw. wie soll ein solche Mega-Demo dann auf
Festplatte laufen?
Beispiele auf dem XL:
Intros oder einzelne Demos: Titelbilder vom Abbuc-Magazin
Megademos: Das Halle-Projekt, HARD-Megademo, Slight-Megademo, The Top 3, Sweet
Illusions von den Shadows
Multiparts: The Top 1+2, Shake-Demo, Just-Fancy-Demo
Oft werden Multioparts und Megademos kombiniert z.B. bei Shadows-Megademo. Der Effekt
1 -Part enthält ja mehrere Effekte, obwohl das Demo eine Megademo ist.
Aus welchen Mitgliedern besteht eine Demo-Gruppe?
Diese Frage ist schwer zu beantworten, da jede Demogruppe ihren eigenen individuellen Aufbau hat.
Folgende Bezeichnungen haben sich eingebürgert:
-Coder: Sind Programmierer, die eigentliche Denkzentrale einer Demogruppe. Sie
programmieren nächtelang an neuen Effekten und Routinen.
-Grafiker: Grafiker haben meistens keine Ahnung vom Programmieren, sind aber für Logos,
Grafiken, Zeichensätze usw. verantwortlich.
-Musiker: Sie schreiben den Demo-Screens passende Musik auf den Leib. Wie in einem guten
Film, sollte die Musik zur Action passen, d.h. z.B. langsame Musik bei einem schnellen
Grafikdemo hat wohl wenig Sinn. Kommt aber auf den Geschmack an und was für eine
Stimmung erzeugt werden soll.
-Swapper: Sie sind für die Verbreitung der Demo zuständig. In den Zeiten der Raubkopiererei, als
Demos eigentlich nur von den Raupkopierern geschrieben wurden, um Intros für ihre
Cracks zu haben, hatten Swapper die Aufgabe, die Kontakte zu den Tauschpartnern zu
pflegen. Heute aber so gut wie nicht mehr vorhanden, da die Demo-Szene sich von der
Raupkopiererszene, Gott sei Dank, gelöst hat. There is no coding like demo coding!
Natürlich können mehrere Aufgaben in einer Person vereint sein. Sollte man allein Programmieren
(so wie ich), stellt sich oft die Frage, wo bekomme ich z.B. Musikstücke oder Grafiken her. Haben
sich mehrere Leute zusammengeschloßen, die unterschiedliche Aufgaben wahrnehmen, so wird die
Demoprogrammierung auf jeden Fall einfacher und weniger zeitaufwendiger, die Demos sehen
proffessioneller aus.
Die einzelnen Begriffbestimmungen für die jeweiligen Effekte spare ich mir hier im Moment noch
auf. Sie sind meistens aus dem Englischen abgeleitet und bezeichnen die Art des Effektes, das
Aussehen oder die Programmierung (z.B. Plasma-Effekt soll eben Plasma darstellen). Dabei sind der
Phantasie keine Grenzen gesetzt.
Kommen wir nun endlich zum eigentlichen Thema, dem Demoprogrammieren. Folgende Tools
sollte man haben: Einen Assembler, von dem man die Start-Adresse weiß oder zumindest resetfest
ist. Turbo-Basic, mit dem man seine Tabellen berechnet bzw. Routinen zuerst testen kann. Es ist
einfacher erst einen Algorithmus in Basic zu schreiben und diesen dann in Assembler umzusetzen.
Mehr benötigt man eigentlich nicht. Ein Malprogramm wie z.B. Rambrandt XL wäre von Vorteil.
Einen Zeichensatzeditor für eigene Fonts. Was man aber unbedingt benötigt, ist eine genaue
Beschreibung der XL Hardware, vor allem des Antics und des GTIAs. Ich benutze bzw. kann
folgendes Buch schon auswendig, nämlich von Data Becker "Atari intern". Und ganz wichtig ist
Kreativität. Grundkenntnisse in Assembler sollten vorhanden sein. Ein Demo besteht meistens nicht
mehr als aus LDAs und STAs !!! (In Basic: Peeks und Pokes)
Wie geht man nun an ein neues Demo heran. Man könnte sich auf ein Blatt Papier eine Skizze
machen, welchen Effekt man darstellen möchte. So habe ich das Titel-Demo für das Magazin 42
entwickelt. Die Idee dazu entstand in einer Vorlesung. Ich wollte, fasziniert durch die
Pixel-Routinen anderer Demos, auch eine schnelle Plot-Routine schreiben. Wie geht man aber an die
Sache heran. In Assembler habe ich ja keine Hilfsbefehle wie in Basic, so scheidet also ein
Plot-Befehl aus, genauso wie umfangreiche Berechnungen. Die Idee war, das vom oberen
Bildschirmrand Pixel herunterfallen sollten. Ich stand nun vor zwei Problemen: Die Pixelroutine
sollte schnell sein, damit kein Geflackere auftritt und die Pixel sollten wie Gummibälle realistisch
nach unten anhand einer physikalischen Formel fallen. Da man in Assembler keine Funktionen an
sich hat, gab ich die Idee vorerst auf.
Doch man könnte in Turbo-Basic eine Tabelle berechnen, die die Y-Koordinaten anhand einer
physikalischen Formel berechnet und im RAM ablegt. Meine Pixel-Routine mußte also nur die
Werte aus dem Ram holen und die Pixel setzten. Das war eigentlich das ganze Geheimnis.
Diese Tabelle war schnell berechnet, nun folgte das eigentliche Problem, nämlich eine schnelle
Pixelroutine zu schreiben.
Meine Pixel-Routine entstand zu erst im Kopf. Ich hatte schon im Jahre 1990 (glaube ich) eine
Routine geschrieben. Ich war richtig stolz, sie war zwar in Assembler entwickelt worden, was nicht
heisst dass sie schnell war. Ich hantierte mit LSR und ASL, was auch nicht gerade zur
Geschwindigkeit beitrug. Dann kam mir aber die Idee, die ganzen Berechnungen schon vorher
für jede Y-Koordinate durchzuführen und in einer Tabelle zu speichern. So konnte ich einfach die
Y-Koordinate als Offset fuer eine Tabelle benutzen, die den Wert für den Bildschirmspeicher
enthielt. Die X-Koordinate wurde als Offset für die Bildschirmadresse benutzt. So mussten auch
keine Bildschirm-Adressen-Berechnungen stattfinden.
Als Grafikstufe wähle ich Gr.8. Die Auflösung beträgt 256 x 128 Pixel. Diese Werte passen
hervoragend zum Konzept (256 passt genau in ein Register).
Hier nochmal genauere Überlegungen, wie ich ein Pixel setze:
Eine Bildschirmzeile in Gr.8 besteht aus 40 Bytes. Da ich aber nur 256 Pixel in X-Richtung
darstellen wollte, mußte ich den Screen verkleinern. Dabei hilft der Antic. Durch ein simplen LDA
#33, STA $d400 schalte ich auf 256 Pixel-Darstellung um. In Basic läßt sich das durch POKE
559,33 testen. Das Bild ist auch in der Mitte vom Schirm mit einem etwas größeren Rand rechts und
links. Eine Zeile besteht jetzt aus 32 Bytes.
Um ein Pixel zu setzen, muss man zuerst wissen in welchem Byte das Pixel zu setzen ist. In Gr. 8
besteht ein Byte aus 8 Pixel, d.h. jedes Bit repräsentiert ein sichtbares Pixel.
Folglich erhält man das Byte, indem man die X-Koordinate durch 8 teilt. (Kommastellen
vernachlässigen!) Man multipliziert diesen Wert nun wieder mit 8. Nun zieht man von der
X-Koordinate diesem Wert ab und erhält die Nr. des zu setzenden Pixels. Anhand von einer
Tabelle ermittelt man den Wert, den man in das Bildschirmram schreiben muss. Die
Bildschirmzeile erhält man, indem man die Y-Koordinate mit der Zeilenlänge multipliziert (hier mit
32, da eine Zeile 32 Bytes hat). Wer will kann dies anhand des Taschenrechners mal ausprobieren,
hier ein kleines Beispiel:
Die Routine soll ein Pixel an der x-Koordinate 83 und an der y-Koordinate 50 setzen. Gehen wir nun
anhand der obigen Ausführungen vor. 83 geteilt durch 8 ergibt 10,375. Wir vernachlässigen die
Nachkommastellen, d.h. wir erhalten den Wert 10. Jetzt wissen wir, daß wir in das 10. Byte in der
noch zu ermittelnden Bildschirmzeile einen Wert schreiben müssen. Jetzt aber welchen Wert? Wir
multiplizieren 10 wieder mit 8 und erhalten 80. Dieser Wert stellt die x-Koordinate des 10. Bytes dar,
da 1 Byte 8 Bit bzw. hier Pixel hat. Jetzt ziehen wir 83 von 80 ab und erhalten als Ergebnis 3. Diese 3
sagt uns, wir müssen das 3. Bit von links setzen. Welchen Wert haben aber die einzelnen Bits?
Anhand einer Tabelle ermitteln wir nun den Wert, den wir in das Bildschirm-Ram schreiben müssen.
Die Tabelle lautet 128,64,32,16,8,4,2,1. Der 3. Wert von links ist folglich 32. Jetzt haben wir den
Wert, aber uns fehlt noch die richtige Bildschirmzeile. Deren Adresse wird in unserem Beispiel
ermittelt, indem wir y-Koordinate 50 mit 32 multiplizieren, da eine Bildschirmzeile 32 Bytes breit
ist (32*8 Pixel=256 Pixel pro Bildzeile). 50*32 ergibt 1600, d.h. wir müssen 1600 Bytes zu der
Anfangsadresse des Bildschirm-Rams dazuaddieren. Zu dieser Adresse, die ja die Anfangsadresse
der 50. Zeile ist, zählen wir noch die oben ermittelten 10 dazu und haben die gesuchte Adresse. In
diese Adresse schreiben wir den Wert 32 und sofort erscheint unser Pixel an der Koordinate 83,50.
Da aber Multiplikationen in Assembler recht langsam und kompliziert sind, könnte man doch schon
vorher die Anfangsadressen jeder Bildschirmzeile in einer Tabelle ablegen, getrennt nach Low- und
Hi-Byte (hier scrtabl,h).
Nun die alte Routine aus dem Jahre 1990:
CLC ;2 Carry-Bit löschen wegen LSR+ASL
LDA XPOS ;3 X-Koordinate
LSR ;/2 ;2 durch 8 teilen (LSR)
LSR ;/2 ;2
LSR ;/2 ;2
STA BYTE ;MERKEN ;3 jetzt hat man das gesuchte Byte
ASL ;*2 ;2 im Screen-Ram. Dieses wird wieder
ASL ;*2 ;2 mit 8 multipliziert (ASL)
ASL ;*2 ;2
STA MERK ;MERKEN ;3
SEC ;2 Carry-Bit setzten wegen SBC
LDA XPOS ;3 Xpos-Byte=Nr. des Pixels
SBC MERK ;3
TAX ;2 Nr. ins X-Register als Offset
LDA PIXTAB,X ;4 Wert aus der Tabelle holen
STA PIXEL ;3
LDX YPOS ;Y-KOORDINATE ;3
LDA SCRTABL,X ;BILDSCHIRMADRESSE AUS TABELLE ;4
STA V0 ;3
LDA SCRTABH,X ;4
STA V0+1 ;3
LDY BYTE ;OFFSET FÜR ZEILE ;3
LDA (V0),Y ;WERT AUS BILDSCHIRM-RAM HOLEN;5
ORA PIXEL ;UND MIT PIXEL VERKNÜPFEN ;3
STA (V0),Y ;WIEDER INS RAM ZURÜCK ;6
RTS ;6
SUMME TAKTZYKLEN ;80
PIXTAB DFB $80,$40,$20,$10,$08,$04,$02,$01
Zur Erinnerung: Ein Byte hat 8 Bit (kein Scherz!!!):
Jedes Bit representiert ein sichtbares Pixel.
Dual Hexadezimal
%00000000 ;$00
%10000000 ;$80
%00010000 ;$10
%10000001 ;$81
Pixtab enthält also die Werte, die in den Bildschirm geschrieben werden müssen, damit bestimmte
Pixel gesetzt werden.
Kurz nochmal der Aufbau des Bildschirmrams in Gr.8:
Anfangsadressen der Bildschirmzeilen werden in einer Tabelle abgelegt
y-Wert: <--------- x-Wert --------> (32 Bytes=256 Pixel)
1. Zeile:$3000 00 00 00 00 00 00 00 00 00 00 00 00 00 ...
2. Zeile:$3020
3. Zeile:$3040
4. Zeile:$3060
...
Man könnte nun statt jedesmal diese arithmetischen Operationen (asl+lsr) und die Pixelermittlung
neu zu berechnen, diese schon vorab in einer Tabelle für jede mögliche x-Koordinate durchführen
und in Tabellen ablegen. Es genügt somit anhand der x-Koordinate den jeweiligen Wert für den
Zeilenoffset und das Pixel aus den Tabellen zu holen. Dies kostet zwar Speicherplatz, aber es läßt
sich einiges an Rechenzeit einspaaren.
So sieht die neue Routine '95 aus:
init jsr tabinit ;vorher natürlich die Tabellen berechnen
...
plot ldx xpos ;Koordinaten stehen in xpos,ypos ;3
lda bytetab,x ;in welches Byte muß Pixel ;4
sta byte ;merken ;3
lda pixtab,x ;welches Pixel setzen ;4
sta pixel ;merken ;3
ldx ypos ;3
lda scrtabl,x ;Screenadresse aus tabelle ;4
sta v0 ;low-byte ;3
lda scrtabh,x ;4
sta v0+1 ;hi-byte ;3
ldy byte ;3
lda (v0),y ;5
ora pixel ;3
sta (v0),y ;ab damit ins screen-ram ;6
rts ;das war's ;6
SUMME TAKTZYKLEN ;57
tabinit ldx #0 ;Hier werden die Tabellen vorbereitet
lda #0 ; aus Sicherheitsgründen alles auf
l2 sta scrtabl,x ;0 setzen (Ich trau dem Entpacker nicht)
sta scrtabh,x
sta bytetab,x
sta pixtab,x
inx ;256 mal
bne l2
lda #scradr.l ;Screen-adresse für
sta v0 ;jede Zeile in die
lda #scradr.h ;Tabelle scrtabl,h
sta v0+1 ;speichern, damit
ldx #0 ;der Plot schneller
l3 lda v0 ;ist.
sta scrtabl,x ;In dieser Tabelle stehen die
sta v0+1 ;Anfangsadressen der Bildschirmzeilen
sta scrtabh,x ;getrennt in Low- und Hibyte
clc
lda v0
adc #32 ;Zeilen-Offset (32 Bytes pro Zeile)
sta v0
lda v0+1
adc #0
sta v0+1
inx ;für 256 Zeilen, (dargestellt werden nur 128)
bne l3
ldx #0 ;Byte Tabelle vorbereiten
l4 txa ;für jede x-position.
clc ;Carry-Bit löschen
lsr a ;Xpos/8 ergibt
lsr a ;Spalten-offset
lsr a ;--> 3 mal nach rechts schieben
sta bytetab,x ;In welchem Byte Pixel zu setzen ist
inx ;bis xpos=255
bne l4
ldx #0 ;Pixel-Werte vorbereiten
l5 lda bytetab,x ;diese werden direkt (später durch Plot)
clc ;in den Bildschirm geschrieben
asl a ;*2 Bytewert*8
asl a ;*2
asl a ;*2
sta byte
sec ;Subtraktion
txa ;Xpos-Bytepos=Pixelnr.
sbc byte
tay ;merken in y-register
lda pixwert,y ;Pixel-Wert
sta pixtab,x ;in Tabelle
inx
bne l5
rts
pixwert DFB 128,64,32,16,8,4,2,1
Wie man aus der Summe der Taktzyklen erkennen kann, ist die zweite Routine schneller, was zur
Folge hat, daß mehr Pixel pro VBL dargestellt werden können, ca. 35% mehr. Es lassen sich weitere
Geschwindigkeitsoptimierungen vornehmen, wie z.B. statt STA (V0),y könnte man die Werte direkt
in den Code statt in V0 schreiben.
Diese Routine setzt jetzt Pixels an jeder beliebigen Koordinate. Eine Tabelle mit den fallenden
Y-Werten wurde auch schon berechnet.
Ablaufen soll der Effekt folgendermaßen:
Zufällig wird ein Pixel in der ersten Zeile gesetzt. Danach wird die Y-Positionen jede 1/50 Sekunde
verändert, indem die Routine den neuen Y-Wert aus der Tabelle holt. Das alte Pixel wird gelöscht.
Das Löschen kann durch die gleiche Routine erreicht werden, man verwendet statt "ora pixel" eben
"and pixel" und eine andere Tabelle für die Pixelwerte. Sie enthält die Werte zum Ausmaskieren von
Bits.
Die Plot-Routine soll erstmal mit einem Pixel laufen, wenn dies klappt wird sie ausgedehnt auf max.
255 Pixel. Im Demo steigt die Zahl der fallenden Pixel auf 96 kontinuierlich.
Hier die kleine Routine für ein Pixel:
;fallende Pixel Entwicklungstext
;(c) 02.05.95
phase = Variable, die angibt, was mit dem Pixel gerade geschieht.
Phase = 0 : Zufällige X-Position ermitteln (0-255) mit 'LDA 53770'
Phase = 1 : Zähler auf 0 setzen, Phase auf 2 setzen
Phase = 2 : Zähler ist Offset für die Tabelle mit den Y-Werten
In dieser Phase wird das Pixel jeden VBL bewegt bis Pixel aus dem Bildschirm
"fällt". Zähler wird jeden VBL um 1 erhöht.
...
lda #0
sta zaehler
lda 53770 ;Zufallszahl holen
sta xpos
loop ldx zaehler
lda ypostab,x ;Werte aus Tabelle holen
sta ypos ;xpos ist bereits gesetzt
jsr plot ;Punkt setzen
inc zaehler ;Zähler erhöhen
lda zaehler
beq exit ;zaehler=0 (=255)?
...
exit lda #0
sta phase ;neue Phase
rts
Diese kleine Routine gilt nur für 1 Pixel. Erweiterbar ist sie auf max. 256,indem man eine Zähl-,
Xpos- und Phasentabelle einführt (für jedes Pixel). Statt "lda zaehler" nun "lda zaehltab,x" usw.
Jetzt hat man den Grundstock für eine Demo. Auf der Magazindiskette ist ein Beispiel-Source-Code,
bei dem zufällig die Pixel herunterfallen.
HeAvEn, 10.06.96
published in Abbuc Magazine #42
nadkar@fh-pforzheim.de