Nachdem wir, ST-spezifisch gesehen, durch unsere Bildungsserie in den letzten Monaten eine kleine Pause eingelegt haben, kommen wir in dieser Ausgabe wieder zu den Wurzeln des STs zurück. Nach langer Tips & Tricks-Abstinenz haben wir uns etwas ganz Trickreiches ausgedacht und veröffentlichen hier eine LUPE, die in Echtzeit einen Ausschnitt des STs auf den vollen Bildschirm vergrößert. Dabei gehen wir nicht nur auf den Algorithmus der Lupe ein, sondern wagen uns auch an diverse Interrupts wie Maus, VBL und das XBIOS. Lassen Sie sich überraschen…
Bitte haben Sie keine Furcht vor dem langen Listing, das sie vielleicht abtippen müßten. Da ich selbst ein Gegner davon bin (Wer tippt beispielsweise seitenlange DATA-Zeilen ab?), überlegte ich lange, ob wir dieses Listing veröffentlichen sollten. Wir taten es nun unter dem Aspekt, daß man, durch die Vielfalt der angesprochenen Problemlösungen, sehr gut daraus lernen kann, weil es genau dokumentiert ist und in dieser ST-Ecke erklärt wird. Ich möchte auch Assembleranfänger dazu aufrufen, sich dieses Programm unter den Nagel zu reißen, denn man findet ein paar sehr interessante Tricks und lernt dabei die etwas unbekannteren Befehle wie TAS oder MOVEP kennen. Sollten Sie das Programm tatsächlich auch verwenden, finden Sie wie immer den Quellcode sowie das ausführbare Programm auf der Monatsdiskette. Der Assemblercode wurde übrigens für den Metacomco-Assembler geschrieben, ist aber leicht auf den Digital-Research-Assembler umsetzbar. Trotzdem werden wir in Zukunft auch wieder zu kleineren Listings zurückkommen.
Sie werden im Laufe der ST-Ecke mitbekommen, daß einige Zeiger des Betriebssystems (XBIOS, Maus und VBL) ganz legal verbogen werden, um die Lupe sauber mit möglichst vielen Programmen laufen zu lassen. Genau diese eigenen Routinen sind neben den eigentlichen Lupenroutinen sehr interessant und sollen ausführlich erklärt werden - aber gehen wir vom Start des Programms aus:
Wird ein Programm gestartet, reserviert das GEMDOS prinzipiell erst einmal jeglichen Speicher (man kann ja nie genug kriegen), der dann bis auf den tatsächlich vom Programm benötigten wieder freigegeben werden muß. Da wir natürlich nicht den gesamten Speicher benötigen, berechnen wir die Länge unseres Programmes (inklusive Datenbereich) und übergeben das der GEMDOS-Routine M_SHRINK, die den reservierten Speicherblock auf die gewünschte Länge reduziert. Gesagt, getan, beginnen wir mit den weiteren Vorarbeiten: Cursor ausschalten, Copyright über unsere kleine einfache Routine CONOUT ausgeben.
Als nächstes verbiegen wir die Maus-Interruptserviceroutine, deren Adresse sich in einer Struktur befindet, die wir über XBIOS’ KBDVBASE() erfahren können (siehe ST-Ecke ‘Der ST läßt das Mausen nicht...’). Ander 16. Stelle (Byte) steht der Vektor der Mausroutine. Direkt vor unserer eigenen Maus-Interruptserviceroutine steht eine ‘magic number’. Eine solche magische Nummer dient dazu, ganz spezielle Dinge (hier unsere eigene Mausroutine) eindeutig zu identifizieren. Bemerken wir, daß vor der aktuellen Mausroutine diese Nummer steht, so wissen wir, daß dies unsere Routine ist, die Lupe also schon einmal vorher gestartet wurde, und wir verabschieden uns sang- und klanglos. (Eine ähnliche und etwas ausführlichere Methode befindet sich am Beginn der VBL-Routine, aber dies erklären wir im Zusammenhang mit jener Routine.) War es der erste Start der Lupe, speichern wir den Einsprung der alten Mausroutine und fügen unsere Adresse ein.
Ohne allzuviel vorwegzunehmen und zu verraten (der Trick mit der Lupe wird erst unten enthüllt), möchte ich hier den Unterschied zwischen der physikalischen und logischen Bildadresse kurz erläutern. Bei der physikalischen Adresse handelt es sich um die Anfangsadresse des Speicherbereichs, den Sie tatsächlich auch auf Ihrem Monitor zu sehen bekommen. Interessanterweise muß das aber nicht unbedingt die Adresse sein, die auch die Routinen benutzen, wenn zum Beispiel eine Linie gezogen oder ein Text geschrieben wird, was im allgemeinen über GEM und damit über LINE-A geschieht. Diese Routinen greifen nämlich auf die logische Bildadresse zu, so daß sie zum Beispiel in einen Bildbereich vom Programm aus zeichnen können, ohne daß es der Benutzer bemerkt. Nachdem dann Ihr Bild fertig ist, ändern Sie Ihre logische zur physikalischen Adresse. Klar, nicht? Da für unsere Lupe das Bild interessant ist, welches ursprünglich (also bevor es unsere nette Lupe gab) auf dem Bildschirm zu sehen war, holen wir uns die physikalische Adresse über PHYSBASE und speichern diese zwischen. Mit den nächsten Zeilen setzen wir die Adressen für unsere Bildspeicher. Die Lupe benötigt zwei Bildschirmspeicher: warum und wieso, das erfahren Sie unten, und deshalb wollen wir auch zunächst die Unterroutine create table außen vor lassen (obwohl ich glaube, daß den ersten Lesern schon langsam ein Licht aufgeht, wie die Lupe vielleicht funktionieren könnte).
Im Laufe des Programms greifen wir auf die Mauskoordinaten zu, um zu wissen, welcher Teil des Bildschirms um die Maus vergrößert werden soll. Dazu bekommen wir (siehe unten) relative Koordinaten vom Tastaturprozessor geliefert. Wir holen uns am Anfang über die AES-Routine graf_mkstate die momentanen absoluten Mauskoordinaten als Anfangskoordinaten und addieren im Mausinterrupt die relativen Koordinaten, da wir die AES-Routine im Interrupt nicht aufrufen können. Um auf graf_mkstate zugreifen zu können, haben wir uns natürlich als Applikation anzumelden.
Damit alles so richtig sauber funktioniert, müssen wir uns in den Vertical-Blank-Interrupt und in den XBIOS-TRAP einhängen. Ganz kurz für alle Einsteiger: Während der Monitor den Bildschirm darstellt, rast der Elektronenstrahl im Zickzack immer von rechts nach links und dabei langsam nach unten (siehe auch Bildwerkstatt ATARI ST 12/88 von Jörg Drücker und Alexander Beller). Ist er unten angekommen, wird er ausgeschaltet (Dunkeltastung) und rast wieder von der rechten unteren in die linke obere Ecke. In dieser Zeit, die man ‘vertical blank’ nennt, hat der Rechner Zeit, einige Dinge zu tun und bietet die Möglichkeit, ein paar kleine Routinen, die sich fein säuberlich anmelden, immer zu diesem Zeitpunkt auszuführen. Voraussetzung ist, daß die Gesamtheit aller VBL-Routinen die Zeit des VBL nicht überschreitet. Unsere Lupe ist eines dieser Programme und klinkt sich daher in die Kette der Programme ein. (Wie Sie unten sehen, werden wir sogar dafür sorgen, daß wir nur alle vier VBL-Aufrufe tatsächlich etwas tun, um den Rechner nicht allzusehr zu belasten.) Um uns in die Liste der Routinen einzutragen, die aufgerufen werden sollen, müssen wir auf eine Adresse zugreifen, die im vom Prozessor geschützten Speicherbereich liegt, was nur im SUPERVISOR-Modus geschehen darf. Falls der Benutzer die physikalische Bildschirmadresse ändert, wird er dies über eine XBIOS-Routine machen. Damit auch wir dies mitbekommen, hängen wir uns zusätzlich in den XBIOS-Einsprung, womit wir eine weitere Adresse im geschützten Speicherbereich ändern müssen. Das Ausführen der Routine INSTAL im Supervisormodus erreichen wir durch die Routine SUPEXEC des XBIOS.
Nach der Initialisierung aller Vektoren legen wir eine kleine Pause ein und verabschieden uns dann... aber halt: Wie Sie sicherlich schon erkannt haben, handelt es sich bei unserer Lupe um ein Programm, das resident im Speicher verankert wird und daher am Ende der Initialisierung nicht wieder entfernt werden darf. Dazu teilen wir dem Betriebssystem mit, welcher Speicherbereich nach dem Verlassen des Programms im Speicher liegenbleiben soll. Nachdem wir dies mit KEEP_PROCESS über GEMDOS getan haben, verabschiedet sich unser Programm endgültig, denn es wird ja nur ab und an gebraucht, nämlich dann, wenn ein VBL-Interrupt auftritt, die Maus angesprochen wird oder der Benutzer die physikalische Bildschirmadresse ändert.
Schauen wir uns zunächst den XBIOS-Trap an. Alle Betriebssystemfunktionen werden über eine softgesteuerte Unterbrechung, einen sogenannten TRAP ausgelöst, dem wiederum eine Nummer zugeordnet wird. So hat zum Beispiel GEMDOS die Nummer 1 (TRAP #1), BIOS die Nummer 13 und XBIOS die 14. Die Adressen der daraufhin ausgeführten Routinen befinden sich ab Adresse $80 (plus Nummer, mal 4). Die Parameter der Routinen werden so auf den Stack gelegt, daß der letzte Parameter als erstes auf den Stack kommt. Die Nummer der Routine (SUPEXEC hat beispielsweise die Nummer $26) steht als letzter Parameter als Wort auf dem Stack. Nachdem die Routine durchgeführt worden ist, werden das Statusregister und der Programmzähler, die zuvor gerettet worden waren, wiederhergestellt, die Parameter befinden sich aber immer noch auf dem Stack, so daß der Stackpointer korrigiert werden muß. Wichtig ist, daß wir prüfen, ob das Programm, das den XBIOS-TRAP ausgelöst hat, im Supervisor-Modus war oder nicht, denn abhängig davon liegen die Parameter auf dem Supervisor- oder Userstack. Nachdem wir dies in unserer Routine durch Testen eines Bits im Statusregister geklärt haben, schauen wir, ob die Routinen PHYSBASE oder SETSCREEN vom Benutzer aufgerufen worden sind. Ist dies der Fall - dies erkennen wir an der Funktionsnummer -, merken wir uns den Parameter, der die physikalische Bildschirmadresse enthält und speichern ihn in der Variablen Physbase ab. Da man mit SETSCREEN auch andere Werte setzen und das Setzen eines bestimmten Parameters durch -1 verhindern kann, prüfen wir diese Tatsache zuvor ab. Danach springen wir in die Original-XBIOS-TRAP-Routine, was wir uns in der Initialisierung durch das Retten der Originaladresse in XBIOS VEC ermöglicht haben.
Wichtig für unsere Lupe ist es zu wissen, an welcher Stelle sich die Maus befindet. Zu diesem Zweck hängen wir uns in den Mausvektor. Was passiert nun in der Mausroutine, und wie können wir sie für unsere Zwecke nutzen? Im ATARI ST befindet sich ein eigener kleiner Prozessor (ein 6803 der MOTOROLA-6800-Familie, seines Zeichens Single-Chip-Processor), der für die Unterstützung der Tastatur, der Maus und des Joysticks zuständig ist. Die Kommunikation mit dem Rest der Welt geschieht über Interrupts und Pakete. Dadurch, daß die Maus durch einen Interrupt zur Ausführung kommt, funktioniert sie praktisch immer, auch dann, wenn die Floppy oder Harddisk angesprochen wird. [Bewegen Sie einmal auf dem PC (IBM & Co) die Maus, während ein Diskettenzugriff läuft: Zunächst passiert nichts, und nach dem Zugriff holt die Maus das Versäumte nach...] Wenn nun ein solcher Interrupt ausgelöst wird, sendet der Chip ein Datenpaket von 3 Bytes, die wie folgt definiert sind:
Header: Die gesetzten Bits des Wertes $F8 (11111000) kennzeichnen, daß es sich um ein Mauspaket handelt. Die beiden unteren Bits zeigen zusätzlich an, welche der beiden Tasten gedrückt worden sind. Daher muß man, um das Mauspaket zu erkennen, mit den Werten $f8 bis $fb rechnen.
DeltaX: relative X-Mausposition, die vorzeichenbehaftet ist und damit zwischen -128 und +127 liegt
DeltaY: relative Y-Mausposition; auch dieser Wert liegt zwischen -128 und + 127.
Die Adresse des Mauspakets steht beim Einsprung in die Mausserviceroutine im Register A0! In unserer Mausroutine fragen wir auf den eben angesprochenen Header ab und speichern die beiden relativen Koordinaten, welche wir gleich zu unseren internen absoluten addieren. Dabei ist aber zu beachten, daß wir unsere absoluten Koordinaten nicht überschreiten - deshalb wird auf die entsprechenden Grenzen abgefragt. Das wäre auch schon fast alles, was es zu unserer Mausroutine zu sagen gäbe. Nebenbei: Es wäre übrigens ein leichtes, in diese Routine eine Verdoppelung der Mausgeschwindigkeit (sogenannte QUICKMOUSE) zu implementieren - man müßte nur die relativen Mauswerte manipulieren. Aber denken Sie daran, daß dies geschehen muß, bevor Sie selbst mit den Koordinaten arbeiten, denn auch unsere eigene Lupe soll diese Verdoppelung mitbekommen. Dies ist übrigens auch der Grund dafür, daß eine vorhandene Quickmouse-Utility vor der Lupe geladen werden muß, denn wenn die Mauskoordinaten nach dem Aufruf der Lupe verdoppelt werden, bemerkt dies die Lupe nicht, und die ST-internen Absolutkoordinaten der Maus laufen doppelt so schnell wie die unserer Lupe.
Sicherlich ist es nicht wünschenswert, die ganze Zeit mit der Lupe zu arbeiten. Deshalb wurde ein Umschalter für die unterschiedlichen Modi (normal, 2fach-, 4fach-...Vergrößerung) eingebaut. Das Umschalten geschieht über die Tastenkombination ALTERNATE-SHIFT-SHIFT, welche wir natürlich im Programm irgendwie abfragen müssen. Der Status dieser Tasten ist als Bits kodiert in einer bestimmten Adresse zu finden - und genau damit beginnt das Problem. In älteren TOS-Versionen (vor 1.2) wurde diese Adresse nicht bekanntgegeben, so daß sich viele Programmierer auf die Suche begaben und eine Adresse fanden, in der der Status zu finden ist: $E1B. Wie das Leben so spielt, wurde diese NICHT-dokumentierte Adresse von ATARI ab der TOS-Version 1.2 geändert. Aber dafür gibt es ab sofort eine saubere Methode, die Adresse herauszubekommen, was aber, es sei noch einmal erwähnt, erst ab TOS-Version 1.2 funktioniert. An der Adresse $4f2 findet man die Adresse der Systemvariablen _sysbase, die auf den sogenannten Betriebssystemheader zeigt, in dem allerlei interessante Informationen zu finden sind. Da uns zwei Einträge interessieren, möchte ich ihn in Listing 1 vollständig als Struktur aufführen.
Aus dieser Struktur greifen wir uns zwei Einträge heraus: zum einen os_version und zum anderen kbshift. In os_version schauen wir nach, ob es sich um ein Betriebssystem handelt, das die Versionsnummer 1.2 oder folgende hat. Ist dies der Fall, befindet sich im Header unter kbshift die Adresse des Tastaturstatus’, die wir dann in unserem Programm benutzen. Ist die Versionsnummer kleiner, greifen wir auf die schon oben erwähnte Adresse $E1B zu. Wenn nun die Tasten ALTERNATE-SHIFT-SHIFT gedrückt werden, überprüfen wir das in jedem VBL-Interrupt und verwerten dies, indem wir einen Modus-Zähler erhöhen. Es ist allerdings wahrscheinlich, daß wir das Drücken und Wiederloslassen nicht in einer 70stel (oder 50stel) Sekunde schaffen, so daß der Zähler mehrfach erhöht werden würde, was natürlich nicht passieren darf! Wir merken uns den Status der Tasten des letzten VBLs und vergleichen ihn mit dem momentanen. Waren die Tasten im letzten VBL gedrückt, und der momentane Status ist jetzt gelöscht, sind die Tasten losgelassen worden, und wir erhöhen unseren Modus. Dieses Verfahren ist in seiner Art recht häufig anwendbar, deshalb sollten Sie es sich etwas genauer anschauen. Sie finden es in der Routine GET_MODE.
typedef struct {
int os_entry; /* Einsprungadresse ins Betriebssystem */
int os_version; /* Betriebssystemversion */
long os_start; /* Startadresse des OS-Codes */
long os_base; /* Anfangsadresse des Betriebssystems */
long os_memboot; /* Beginn des freien Speichers */
long os_shell; /* Shell */
long os_magic; /* Zeiger auf GEM-magic-Variable ->$8765432 */
long os_gendat; /* Erstellungsdatum des TOS im BCD-Format */
int os_palmode /* unterstes Bit gesetzt bedeutet PAL, sonst NTSC */
int os_gendatg; /* Erstellungsdatum im GEMDOS-Format */
/* folgende Einträge sind erst ab Version 1.2 des TOS verfügbar ! */
long *_root; /* Zeiger auf Speicherblockliste(->GEMDOS INTERN
Sonderheft oder ST-Computer 9/87 - Alex Esser */
long kbshift; /* Zeiger auf Adresse, die Tastaturstatus enthält */
PD **_run; /* Zeiger auf PD des aktuellen Prozesses */
}SYSHDR;
Listing 1: Struktur des Betriebssystemheaders
Jetzt habe ich Sie aber lange genug neugierig gemacht! Da wir alle Vorarbeit geleistet haben, kommen wir endlich zur Hauptsache: der Lupe. Sie ist in verschiedenen Vergrößerungsstufen (und Auflösungen) realisiert worden. Da aber der Trick beziehungsweise das Schema der schnellen Vergrößerung praktisch dasselbe ist, wollen wir nur die Vierfach-Monochrom-Vergrößerung erklären.
Wenn die VBL-Routine angesprungen wird, prüft sie zunächst in der Systemvariablen _flock, die an der Adresse $43e liegt, nach, ob momentan ein Zugriff auf die Harddisk oder die Floppy (auf gut deutsch: DMA-Betrieb) vorliegt. Ist dies der Fall, verabschiedet sie sich, um einen solchen Peripheriezugriff nicht zu behindern. Andernfalls zählt sie einen Zähler bis zum Wert REPEAT hoch, der hier auf 4 gesetzt ist. Hat der Zähler den Wert erreicht, wird die Lupe aktiviert, so daß sie nur alle vier VBL-Einsprünge aktiv wird. Wie Sie beim Benutzen der Lupe feststellen werden, reicht das völlig aus, und wir belasten die CPU nicht allzu stark. Außerdem überprüfen wir unsere Auflösung, um die richtige Lupe einzusetzen. In GET_RES wird zusätzlich noch überprüft, ob unser(!) Vektor der Mausroutine noch gesetzt ist, oder ob das Betriebssystem nicht vielleicht seinen eigenen Vektor darübergeschrieben hat. Zeigt der Vektor wieder ins ROM, schreiben wir einfach unsere Adresse wieder hinein. Und nun geht’s in die Lupe...
Prinzipiell beruht die Lupe darauf, daß sie den darzustellenden Bildschirm (also den an der physikalischen Adresse) in einer modifizierten Form an einen anderen Speicherplatz kopiert und dann den Videochip (liebevoll SHIFTER genannt) dazu veranlaßt, nicht den Speicher der physikalischen Adresse, sondern nun unseren Speicher darzustellen. Wie das funktionieren soll, wo doch die physikalische Adresse die Adresse ist, die dargestellt wird? Es ist ganz einfach: Das Betriebssystem benutzt diese ‘physikalische Adresse' nur als Zwischenspeicher, wirklich maßgebend ist die Adresse, die im VIDEO-SHIFTER (dem Grafik-Chip des ST) steht. Vor jeder VBL schaut das Betriebssystem nach, ob sich der Wert in der physikalischen Adresse geändert hat. In diesem Fall schreibt es die neuen Werte in den Chip. Nur, was das Betriebssystem kann, können wir schon lange, oder? Wir schreiben also, nachdem wir unser modifiziertes Bild aufgebaut haben, in den Grafik-Chip des ST unsere Adresse. Das Schöne dabei ist, daß das Betriebssystem davon nichts bemerkt, denn seine physikalische Adresse hat sich ja nicht geändert, und somit schreiben alle Routinen brav weiterhin an die Originaladresse.
vorher (1 Byte) | nachher (4 Bytes) |
---|---|
00000000 | 00000000 00000000 00000000 00000000 |
00000001 | 00000000 00000000 00000000 00001111 |
00000010 | 00000000 00000000 00000000 11110000 |
00100101 | 00000000 11110000 00001111 00001111 |
10111010 | 11110000 11111111 11110000 11110000 |
11111111 | 11111111 11111111 11111111 11111111 |
Tabelle 1: Beispiele der vierfachen Dehnung
Nachdem wir nun eine Möglichkeit gefunden haben, einen anderen Speicherbereich auf dem Monitor sichtbar zu machen als den, in den das laufende Programm schreibt, müssen wir noch eine Möglichkeit finden, möglichst schnell unseren Speicher mit dem Inhalt des Originalbildes zu füllen. Allerdings wäre das ja relativ langweilig, denn wir hätten damit nichts gewonnen. Wir wollen das Bild ja modifiziert oder, genauer, ausschnittsvergrößert darstellen. Beachten Sie, daß es dabei wirklich darauf ankommt, schnell zu sein, denn die VBL-Routine darf nicht ewig dauern. Wie also bringt man mit möglichst wenig Aufwand eine rasend schnelle Vergrößerung hin? Wir wollen uns das Prinzip in der monochromen Darstellung anhand der Vierfach-Vergrößerung anschauen. In der monochromen Auflösung wird ein Bildpunkt in einem Bit verschlüsselt. Acht Bildpunkte haben in einem Byte und die 640 Punkte einer Zeile haben in 80 Bytes Platz. Führen wir eine vierfache Vergrößerung durch, so bedeutet das, daß ein Punkt vier Bildpunkte in der Breite und Höhe hat, so daß er durch 4 Bits in einem Byte repräsentiert wird und über 4 Zeilen geht. Das wiederum bedingt, daß wir nur ein Viertel des Bildschirms gleichzeitig darstellen können (was bei einer vierfachen Vergrößerung zugegebenermaßen nicht weiter verwunderlich ist). Stellen wir uns einmal vor, der Punkt an der Koordinaten 0,0 wäre gesetzt und der an der Koordinate 1,0 nicht, so hätte das entsprechende Byte im Originalbild folgende Bitbelegung:
10xxxxxx: 1=gesetzt, 0=gelöscht, x=nicht angegeben.
Vergrößern wir auf das Vierfache, ist ein Punkt 4 Bits breit, so daß die beiden Punkte nun ein Byte ausfüllen, was jetzt folgendermaßen aussieht: 11110000. Wichtig ist, daß auch die Bytes in den drei Zeilen darunter genauso ausschauen, denn unser Punkt ist ja nicht nur viermal so breit, sondern auch viermal so hoch! Gut, das Prinzip der Vergrößerung ist praktisch verstanden: Wir müssen ein Bit auf vier Bits ausdehnen und eine Zeile auf vier. Nun ist das einzelne Setzen von Bits bei einem Umfang von 640x400 Punkten viel zu langsam, und wir müssen uns eine andere Idee ausdenken. Jedes Byte wird bei der Vierfach Vergrößerung auf vier Bytes gedehnt, wobei es 256 Möglichkeiten eines Bytes gibt. Zur Verdeutlichung möchten wir ein paar Beispiele der Dehnungen darstellen (siehe Tabelle 1).
Vergrößern wir unser Ausgangsbild, müßten wir für jedes Byte eine Dehnung durchführen und diese dann in das Zielbild kopieren. Aber wir können uns die Sache vereinfachen, indem wir die schon gedehnten Werte in einer Tabelle stehen haben. Stellen Sie sich die Tabelle wie die meiner Beispiele vor, nur mit dem Unterschied, daß sie vollständig ist und damit 256 Einträge à vier Bytes hat. Wenn ich beispielsweise das Byte mit dem Wert 50 habe, hole ich mir aus dem 50. Tabelleneintrag den schon gedehnten, vier Bytes langen Wert heraus, was praktisch keinen Rechenaufwand bedeutet, und kopiere ihn schnell in mein Zielbild. Das mache ich, ausgehend von der aktuellen X-Koordinate, mit zwanzig Bytes des Ausgangsbildes (Wir können nur 20*8 Bildpunkte darstellen, siehe oben.) und gehe zur nächsten Zeile des Quellbildes. Diese Zeile findet sich im Zielbild vierfach vergrößert vier Zeilen unter der letzten erzeugten Zeile. Haben wir alle Zeilen erzeugt, befinden sich noch Lücken zwischen den Zeilen des Zielbildes - wir haben ja nur eine Zeile erzeugt, also sind die drei dazwischen noch nicht gefüllt. Diese müssen wir nur noch von der Zeile obendrüber kopieren, wobei man durch einen besonderen Assemblertrick 40 Bytes auf einmal (!) kopieren kann!
Die Tabellen werden erst während des Programmablaufes erzeugt, was in der Routine Create_table geschieht. Den genauen Ablauf dieser vertrackten Routinen möchte ich hier nicht schildern, allerdings habe ich für diejenigen, die der Algorithmus interessiert, die Dokumentation einer Tabellenerstellung besonders ausführlich gestaltet. Eine Verständnisfrage zu den Tabellen: Wie groß ist ein Eintrag einer Tabelle für den Vergrößerungsfaktor 8, und wie würde der Eintrag für den Byte wert $73 aussehen? (Erst überlegen:) Die Tabelle hat natürlich eine Eintragslänge pro Wert von acht Bytes und der Wert $73 (01110011) würde folgendermaßen aussehen:
00000000 11111111 11111111 11111111
00000000 00000000 11111111 11111111
Das Erstellen der 8er-Tabelle ist also sehr einfach, da ein Byte entweder nur ganz gefüllt oder gelöscht einem Bit entspricht.
Schauen wir uns nun als Beispiel ZOOM_4 an, und bitte nehmen Sie sich für den nächsten Abschnitt etwas Zeit. Schauen Sie sich beim Lesen tatsächlich auch den Assemblercode an und versuchen Sie, ihn zu verstehen. Zunächst prüfen und setzen wir die Koordinaten des Lupenfensters. Danach holen wir die Adresse unserer Tabelle und setzen die Anzahl der zu kopierenden Zeilen auf 100 (400/4=100). Der Algorithmus besteht prinzipiell aus zwei Schleifen ineinander: Die äußere Schleife sorgt für hundert Zeilen (dbra,d7,zoom_41), die innere für die zwanzig Bytes einer Zeile (dbra d6, zoom_42), wobei immer zwei Bytes in einem Durchgang bearbeitet werden.
Bei Label ZOOM_42 holen wir uns zwei Bytes aus dem Originalbild. Bitte beachten Sie, daß die momentane X-Koordinate nicht unbedingt auf einer Bytegrenze liegen muß, deshalb werden die Bits auf die Wortgrenze geschoben. Durch das Schieben stehen die Bits nun im oberen Wort des Langworts, so daß wir diese wieder durch den swap-Befehl hinuntertransportieren müssen. Zunächst bearbeiten wir das obere Byte, indem wir es um 8 Bits nach unten verschieben. Danach benutzen wir es als Index in unsere Tabelle, wobei wir es wieder mit vier multiplizieren müssen. (Jeder Eintrag ist vier Bytes lang.) Da eine Multiplikation mit 4 einem zweifachen Schieben nach links gleichkommt, können wir das achtfache Schieben nach rechts und das 2fache nach links zu einer Operation ‘6faches Schieben nach rechts’ zusammenfassen. Jetzt interessieren uns nur Werte von 0 bis 1024 (so lang ist unsere Tabelle) und zu diesem Zweck, blenden wir alle oberen Bits aus. Mit move.l 0(a2,d3.w),(a1)+ addieren wir zu der Tabellenadresse in a2 den Index in d3 (und 0), holen uns aus dieser Adresse unseren gedehnten Wert und schreiben ihn an die Zieladresse in a1. Das gleiche Spielchen führen wir nun mit dem zweiten im unteren Teil des Wortes befindlichen Byte durch, nur mit dem Unterschied, daß wir es vorher nicht um acht Stellen nach unten schieben, sondern um zwei Stellen nach oben (mal 4). Interessant ist, daß die Multiplikation hierdurch die zweifache Addition mit sich selbst durchgeführt wird: Das ist schneller als ein zweifaches Schieben nach oben (links)! Haben wir die 20 Bytes hinter uns gebracht, ist die Zeile im Zielbild voll; bis zum Anfang des nächsten Bytes des Quellbildes fehlen aber noch 60 Bytes, die wir zur Quellbildadresse hinzuaddieren - im Zielbild überspringen wir die nächsten drei Zeilen(3*80), da wir diese am Ende durch ein reines Kopieren ergänzen. Sind nun alle 100 Zeilen vergrößert übertragen, wissen wir, daß im Zielbild immer noch Lücken zwischen den einzelnen Zeilen bestehen, die wir durch folgenden Assemblertrick sehr schnell zu füllen wissen:
Es ist hinreichend bekannt, daß der MC68000-Prozessor, trotz seines wirklich variantenreichen Befehlssatzes, keinen Befehl zum Verschieben oder Kopieren von großen Blöcken besitzt. Es gibt aber einen Trick, mit dem man 40 Bytes auf einmal (ehrlich gesagt, in zwei Schritten) kopieren kann. Der Befehl 'movem.l' ist der eigentlich dazu gedacht, eine Reihe von Registern auf einen Schlag abzuspeichern und wieder zu retten. Nun ist jedes Register 4 Bytes lang, und man kann es so einrichten, daß man 10 Register entbehrlich macht. Dadurch liest man mit ‘movem.l (a0)+, d0-d5/a3-a6' von der gewünschten Adresse a0 in die Register d0-d5 und a3-a6 auf einmal 40 Bytes ein und erhöht dabei a0 automatisch um vierzig. Abgespeichert werden die Bytes wieder mit dem komplementären Befehl ‘movem.l d0-d5,a3-a6,Adresse'. Um nun die drei freigebliebenen Zeilen zeilenweise nacheinander zu füllen, kopieren wir die Bytes mit diesem Trick sehr schnell, indem wir zur Zielzeile noch jeweils einen Offset von 40, 40+80 oder 40+160 addieren. Normalerweise würde man vermuten, daß man 80, 160 und 240 addierte, da aber der erste movem.l das a0-Register schon um vierzig erhöht hat, müssen wir vierzig vom Offset abziehen.
Wenn Sie das Listing betrachten, werden Sie bemerken, daß das Programm mit zwei Bildschirmspeichern arbeitet und diese immer umschaltet. Das hat den Vorteil, daß man immer in den Speicher schreibt, den der Benutzer gerade nicht sieht. Daher bekommt er keine Kopierarbeiten zu sehen, und das Bild beginnt nicht zu flackern.
Zum Schluß möchte ich Sie noch auf eine besondere Sache hinweisen. Oben wurde schon erwähnt, daß die Lupe im VBL die Bildschirmadresse im VIDEO-Shifter setzt. Die Adressen der BYTES dafür liegen an der Stelle $FF8201 und $FF8203. Jeder, der sich ein bißchen mehr auskennt, wird zwei Dinge bemerken: Erstens sind es nur zwei Bytes (für eine drei-Byte-Adresse), und zweitens liegen sie zu allem Unglück auch noch auf einer ungeraden Adresse. Das Problem der zwei Bytes erklärt sich, daß es beim ST nur möglich ist, den Bildschirm auf einer Adreßgrenze von 256 Bytes anfangen zu lassen. Das heißt, daß $FF8201 für das High-Byte und $FF8203 für das Mid-Byte der Adresse verantwortlich ist. Das Low-Byte wird vom Shifter automatisch intern auf Null gesetzt. Wären die Adressen nun auf der gerade Adresse zu finden, könnte man recht einfach die Werte hineinschreiben. Für den hier vorliegenden Fall bieten wir Ihnen eine in QUICK zu findende, elegante Lösung mit den Befehl ‘movep’ an, der auf einmal die beiden Bytes eines Wortes mit einem Byte Abstand in den Speicher schreibt und das, bei einem Offset von Eins, sogar an eine ungerade Adresse.
Wie schon erwähnt, haben wir eine weitere Möglichkeit in unser Programm eingebaut, an der wir erkennen können, ‘daß wir wir sind’. Vor dem eigentlichen Einsprung der VBL-Routine steht eine Struktur von drei mal vier Bytes. Im ersten Eintrag steht der Name XBRA, an dem man die Struktur erkennen kann. Im zweiten Eintrag findet sich der Name des Programms, das sich in die VBL eingeklinkt hat, wobei der Name aber nur vier Buchstaben lang sein darf. Im dritten Eintrag haben wir die Adresse, die ursprünglich als Einsprung in die VBL-Programmliste gegolten hat. Durch diese Struktur wäre es sogar einem anderen Programm möglich, unsere Routine wieder auszuhängen, indem sie unsere neue Adresse mit der in XBRA zu findenden Adresse überschreibt: Dadurch wären wir einfach aus der Kette entfernt worden.
Ich hoffe, daß wir Ihnen mit dieser Lupe einen kleinen Einblick in die vielfältigen Möglichkeiten des STs gegeben haben, und daß Sie von dem einen oder anderen Trick etwas lernen konnten. Lassen Sie sich nicht entmutigen, falls Sie Teile nicht verstanden haben, sondern lesen Sie sich den einen oder anderen Absatz ein weiteres Mal vor und schauen Sie Sie sich dazu die passende Stelle im Listing an. Und vergessen Sie nicht, die Lupe auch mal auszuprobieren: Es lohnt sich ganz bestimmt, versprochen....
Stefan Höhn, Alexander Beller
*************************************************
*
*
* REALTIME MAGNIFIER
*
*
* Written by Alexander Beller
*
* (C) Copyright by IMAGIC Grafik
*
*
*************************************************
GEMDOS: equ 1 * Trapvektor GEMDOS
BIOS: equ 13 * Trapvektor BIOS
XBIOS: equ 14 * Trapvektor XBIOS
PRINT_LINE: equ $09 * GEMDOS Funktionen
KEEP_PROCESS: equ $31
M_SHRINK: equ $4A
GET_PHYSBASE: equ $02 * XBIOS Funktionen
GET_RESOLUTION: equ $04
SET_SCREEN: equ $05
SET_CURSOR: equ $15
GET_KBDVBASE: equ $22
GO_SUPER: equ $26
REPEAT: equ 4 * Wiederholfrequenz für
* * den Bildaufbau im VBL
*************************************************
PROGRAM_START:
move.l 4 (sp),a6
lea USER_STACK,sp * Eigenen STACK installieren
*
pea (USER_STACK-BSS_START)+(TEXT_END-PROGRAM_START)+256
pea (a6)
clr.w -(sp)
move.w #M_SHRINK,-(sp) * Nicht benötigten Speicher
trap #GEMDOS * ans System zurückgeben
lea 12(sp),sp
*
*****************************************
*
clr.l -(sp)
move.w #SET_CURSOR,-(sp) * Cursor ausschalten
trap #XBIOS
addq.l #6,sp
*
pea MESSAGE(pc) * Copyright ausgeben
bsr CONOUT
*****************************************
*
move.w #GET_KBDVBASE,-(sp) * Adresse der
trap #XBIOS * Tastatur-Vektortabelle holen
addq.l #2,sp
move.l d0,a0
*
lea KBDVBASE(pc),a1
move.l a0,(a1)+ * Adresse der Vektortabelle sichern
move.l 16(a0),a2 * Aktueller Mausinterruptvektor
*
cmpi.l #$02101968,-4(a2) * Magic-Number vor der Maus-Routine?
beq EXIT * Dann ist Lupe schon installiert
*
*****************************************
*
move.l a2,(a1)+ * Alten Mausvektor sichern
move.l #NEW_MOUSE,16(a0) * Neuen Mausvektor setzen
*
move.w #GET_PHYSBASE,-(sp) * Physikalische Bildschirm
trap #XBIOS * adresse holen
addq.l #2,sp
move.l d0,PHYSBASE
*
move.l #FLIPSCR_1+255,d0 * Basisadresse von Bild 1/2
clr.b d0 * auf einer 256-Byte-Grenze
move.l d0,FLIPBASE_1 * Adresse von Bild1 setzen
add.l d0,FLIPBASE_2 * Adresse von Bild2 setzen
*
bsr APP_INIT * Application anmelden
bsr GRAF_MKSTATE * Mausposition holen
bsr CREATE_TABLE * Tabellen erstellen
pea INSTAL(pc) * Supervisor-Unterprogramm aufrufen
move.w #GO_SUPER,-(sp) * (XBIOS- u.VBL-Vektor umleiten)
trap #XBIOS
addq.l #6,sp
*
bsr PAUSE * Pause einlegen und danach
* * Programm resident beenden
clr.w -(sp)
pea (BSS_END-BSS_START)+(TEXT_END-PROGRAM_START)+256
move.w #KEEP_PROCESS,-(sp)
trap #GEMDOS
*
*****************************************
*
EXIT: bsr PAUSE * Pause einlegen
*
clr.w -(sp) * Programm verlassen
trap #GEMDOS
*
*****************************************
*
* XBIOS-Trap
*
*****************************************
*
NEW_XBIOS:
lea 6(sp),a0 * Zeiger auf SUPERVISOR-Parameter
move.w (a0),d0 * Funktionsnummer vom SSP holen
*
btst #5,(sp) * Aufruf vom SUPERVISOR?
bne.s SVR_CALL * Ja... .
*
move.l usp,a0 * Nein, Zeiger auf USER-Parameter
move.w (a0),d0 * Funktionsnummer vom USP holen
*
SVR_CALL:
cmpi.w #GET_PHYSBASE,d0 * Funktion GET_PHYSBASE?
beq.s F_PHYSBASE * Ja...
cmpi.w #SET_SCREEN,d0 * Funktion SET_SCREEN?
beq.s F_SETSCREEN * Ja...
*
SYS_XBIOS:
move.l XBIOS_VEC(pc),-(sp) * Zum SYSTEMTRAP springen
rts
*
F_PHYSBASE:
move.l PHYSBASE(pc),d0 * Adresse des Hintergrundbildes
rte * zurückliefern
*
F_SETSCREEN:
move.l 6(a0),d0 * Soll Physbase umgesetzt werden?
bmi.s SYS_XBIOS * Nein...
*
move.l d0,PHYSBASE * Neuen Zeiger sichern
move.w #-1,6(a0) * Physbase NICHT umsetzen
bra.s SYS_XBIOS
*
*****************************************
*
* VBL Interrupt
*
*****************************************
*
XBRA:
dc.b 'xbra' * gross oder KLEIN?
dc.l 04031965
XBRA_VBL:
dc.l NEW_VBL * Adresse des eigenen VBL
*
NEW_VBL:
movem.l d0-d2/a0-a2,-(sp) * Register retten
*
move.l $45E,d0 * Screenzeiger gesetzt:
beq.s NO_SCREENPTR * Nein...
*
clr.l $45E * Screenzeiger löschen
move.l d0,PHYSBASE * und neuen Bildvektor übernehmen
*
NO_SCREENPTR:
tst.w $43E * Wenn Floppy oder Harddisk aktiv, dann
bne.s GO_SYSTEM * nichts weiter tun
*
bsr GET_MODE * Modus holen
*
bsr.s GET_RES * Auflösung holen
*
lea COUNTER(pc),a0 * Zähleradresse
subq.w #1,(a0) * Countdown abgelaufen'
bpl.s GO_SYSTEM * Nein....
*
move.w #REPEAT,(a0) * Zähler neuladen
*
bsr.s LUPE * Bild aufbauen
*
GO_SYSTEM:
movem.l (sp)+,d0-d2/a0-a2 * Register laden
*
move.l VBL_VEC(pc),-(sp) * Weiter mit dem System-VBL
rts
*
*****************************************
*
GET_RES:
moveq #3,d0 * Komplett neu
and.b $FFFF8260,d0 * Auflösung in d0 und sichern
*
cmp.w RESOLUTION(pc),d0
beq.s CMP_MOUSE
*
move.w d0,RESOLUTION
clr.b MODE
*
CMP_MOUSE:
lea NEW_MOUSE(pc),a1 * Adresse der eigenen Routine
*
move.l KBDVBASE(pc),a0 * Wurde Mausvektor umgesetzt?
move.l 16(a0),d0 * Aktuellen Vektor holen
*
cmp.l a1,d0 * Mit eigenem Vektor vergleichen
beq.s NO_CHANGE * Ist noch der alte...
*
moveq #23,d1
btst d1,d0
beq.s NO_CHANGE * Wenn nicht, dann alles so lassen...
*
move.l d0,KBDVBASE+4 * Vektor sichern
move.l a1,16(a0) * Eig. Vektor setzen
*
NO_CHANGE:
rts
*
*****************************************
*
LUPE:
move.l PHYSBASE(pc),a0 * Hintergrundbild
move.l FLIPBASE_1(pc),a1 * Aktuelles Zielbild
*
move.b MODE(pc),d0 * Modus holen
beq QUICK * Keine Vergrösserung
*
movem.l d3-d7/a3-a6,-(sp) * Register retten
*
pea LUPE_END(pc) * Return-Adresse auf den Stack
* erleichtert das bedingte Rufen
* von Unterprogrammen
*
move.w RESOLUTION(pc),d1 * Auflösung holen
*
beq ZOOM_LOW * Niedere Auflösung
*
subq.w #1,d1 * Mittlere Auflösung
beq ZOOM_MEDIUM
*
subq.b #1,d0 * High-Res Faktor 8
beq ZOOM_8
*
subq.b #1,d0 * High-Res Faktor 4
beq ZOOM_4
*
bra ZOOM_2 * High-Res Faktor 2
*
*
LUPE_END:
move.l FLIPBASE_1(pc),d1 * Bildadressen tauschen
move.l FLIPBASE_2(pc),d0
movem.l d0-d1,FLIPBASE_1
*
movea.w #$8200,a1 * Adresse setzen
lsr.l #8,d1
movep.w d1,1(a1)
*
movem.l (sp)+,d3-d7/a3-a6 * Register laden
rts
*
*****************************************
*
ZOOM_8:
moveq #40,d2 * Aus der Grösse des Lupenfensters
moveq #25,d3 * wird der Zeiger ins Hintergrund-
move.w #559,d4 * bild berechnet
move.w #349,d5
bsr CALC_SCRPTR
*
adda.w d0,a0 * Offsets auf die Adresse des
adda.w d1,a0 * Hintergrundbildes addieren
*
lea BUFFER_8,a2 * Zeiger auf Tabelle
move.w #255*8,d5 * Maske für den Tabellenindex
moveq #49,d7 * 50 Zeilen pro
* * Bildschirm
ZOOM_81:
moveq #4,d6 * 5 Words pro Zeile
*
ZOOM_82:
move.l (a0),d0 * Daten aus Hintergrundbild holen
addq.l #2,a0 * Stets 2 Bytes pro Durchgang
*
rol.l d2,d0 * Bytes auf Wortgrenze shiften
swap d0 * Bytes nach "unten" holen
move.w d0,d1 * Ergebnis sichern
*
lsr.w #5,d0 * Byte 1 nach "rechts" schieben
* * und mit 8 multiplizierend Index
*
and.w d5,d0 * Index maskieren
move.l 0(a2,d0.w),(a1)+ * Langwort 1 aus Tabelle holen
move.l 4(a2,d0.w),(a1)+ * Langwort 2 aus Tabelle holen
*
lsl.w #3,d1 * Byte 2*8 -> Index
*
and.w d5,d1 * Index maskieren
move.l 0(a2,d1.w),(a1)+ * Langwort 1 aus Tabelle holen
move.l 4(a2,d1.w),(a1)+ * Langwort 2 aus Tabelle holen
*
dbra d6,ZOOM_82 * Nächst. Word bearbeiten
*
lea 70(a0),a0 * Zeiger auf die jeweils
lea 7*80(a1),a1 * nächste Zeilen setzen
*
dbra d7,ZOOM_81 * Nächst.Zeile
* * bearbeiten
*****************************************
*
move.l FLIPBASE_1(pc),a0 * Zieladresse
moveq #49,d7 * 100 Abschnitte à 4 Zeilen
*
*
*
FILL_10:
moveq #1,d6 * 2 x 40 Bytes pro Zeile
*
FILL_11:
movem.l (a0)+,d0-d5/a3-a6 * 40 Bytes nach unten kopieren
movem.l d0-d5/a3-a6,40(a0)
movem.l d0-d5/a3-a6,40+80(a0)
movem.l d0-d5/a3-a6,40+160(a0)
movem.l d0-d5/a3-a6,40+240(a0)
movem.l d0-d5/a3-a6,40+320(a0)
movem.l d0-d5/a3-a6,40+400(a0)
movem.l d0-d5/a3-a6,40+480(a0)
dbra d6,FILL_11
*
lea 7*80 (a0),a0 * Zeiger auf nächste Zeile setzen
dbra d7,FILL_10 * und weiter...
*
rts
*
*****************************************
ZOOM_4:
moveq #80,d2 * Aus der Grösse des Lupenfensters
moveq #50,d3 * wird der Zeiger ins Hintergrund-
move.w #479,d4 * bild berechnet
move.w #299,d5
bsr CALC_SCRPTR
*
adda.w d0,a0 * Offsets auf die Adresse des
adda.w d1,a0 * Hintergrundbildes addieren
*
lea BUFFER_4,a2 * Zeiger auf Tabelle
moveq #99,d7 * Anzahl Zeilen
move.w #4*255,d1 * Maske für Tab.index
moveq #6,d5 * Konstante
*
ZOOM_41:
moveq #9,d6 * 10 Words pro Zeile
*
ZOOM_42:
move.l (a0),d0 * Daten aus Hintergrundbild holen
addq.l #2,a0 * Stets 2 Bytes pro Schleife
*
rol.l d2,d0 * Bytes: Wortgrenze shiften
swap d0 * Bytes nach "unten" bringen
*
move.w d0,d3 * Byte 1 "rechts" schieben
ror.w d5,d3 * mal 4 -> Index
and.w d1,d3 * Index maskieren
move.l 0(a2,d3.w),(a1)+ * Langwort aus Tabelle holen
*
add.w d0,d0
add.w d0,d0
and.w d1,d0 * 2. Byte holen
move.l 0(a2,d0.w),(a1)+ * Langwort aus Tabelle holen
*
dbra d6,ZOOM_42
*
lea 60(a0),a0
lea 240(a1),a1
*
dbra d7,ZOOM_41
*
*****************************************
*
move.l FLIPBASE_1(pc),a0 * Zieladresse
moveq #99,d7 * 100 Abschnitte zu 4 Zeilen
*
FILL_2:
movem.l (a0)+,d0-d5/a3-a6 * 40 Bytes nach unten kopieren
movem.l d0-d5/a3-a6,40(a0)
movem.l d0-d5/a3-a6,40+80(a0)
movem.l d0-d5/a3-a6,40+160(a0)
*
movem.l (a0)+,d0-d5/a3-a6 * 40 Bytes nach unten kopieren
movem.l d0-d5/a3-a6,40(a0)
movem.l d0-d5/a3-a6,40+80(a0)
movem.l d0-d5/a3-a6,40+160(a0)
*
lea 3*80(a0),a0 * Zeiger auf nächste Zeile
dbra d7,FILL_2 * und weiter...
*
rts
*
*****************************************
*
ZOOM_2:
move.w #160,d2 * Hohe Auflösung Faktor 2
moveq #100,d3 * Alles neu ....
move.w #319,d4
move.w #199,d5
bsr CALC_SCRPTR
*
adda.w d0,a0 * Offsets auf Adresse vom
adda.w d1,a0 * Hintergrundbild addieren
*
lea 80(a1),a2
lea BUFFER_2,a3
*
move.w #199,d7 * Anzahl Zeilen
move.w #$1FE,d1 * Maske
moveq #7,d5 * Konstante
*
ZOOM_21:
moveq #19,d6 * 20 Words pro Zeile
move.w (a0)+,d0 * Erstes Word holen
*
ZOOM_22:
swap d0
move.w (a0)+,d0 * Sourcedaten holen
move.l d0,d4 * Daten in d0 erhalten
*
lsl.l d2,d4 * Word zurechtshiften
swap d4 * Zweites Byte in Byte 4
* * Erstes Byte in Byte 3
*
move.w d4,d3 * Erstes Byte holen
lsr.w d5,d3
and.w d1,d3
move.w 0(a3,d3.w),d3
move.w d3,(a1)+
move.w d3,(a2)+
*
add.w d4,d4
and.w d1,d4 * Zweites Byte holen
move.w 0(a3,d4.w),d4
move.w d4,(a1)+
move.w d4,(a2)+
*
dbra d6,ZOOM_22
*
lea 40-2(a0),a0
lea 80(a1),a1
lea 80(a2),a2
*
dbra d7,ZOOM_21
*
rts
*
*****************************************
*
ZOOM_MEDIUM:
move.w #160,d2 * Mittlere Auflösung Faktor 2
moveq #50,d3
move.w #639-2*160,d4
moveq #199-2*50,d5
bsr CALC_SCRPTR
*
add.w d1,d1 * Offsets an mittlere
add.w d0,d0 * Auflösung anpassen
add.w d0,d1 * Offsets auf Adresse vom
adda.w d1,a0 * Hintergrundbild addieren
*
lea 160(a1),a2 * Zeiger auf Hintergrundzeile+1
lea BUFFER_2,a3 * Tabellenzeiger
*
moveq #99,d7 * Anzahl Zeilen
move.w #$1FE,d1 * Maske
moveq #7,d5 * Konstante
*
ME2_10:
moveq #19,d6 * 20 Words pro Zeile
*
ME2_20:
moveq #1,d0 * 2 Planes bearbeiten
*
ME2_21:
move.w (a0)+,d4 * Sourcedaten holen
swap d4
move.w 2(a0),d4
*
lsl.l d2,d4 * Word zurechtshiften
swap d4 * Zweites Byte in Byte 4
* * Erstes Byte in Byte 3
*
move.w d4,d3 * Erstes Byte holen
lsr.w d5,d3
and.w d1,d3
move.w 0(a3,d3.w),d3
move.w d3,(a1)+
move.w d3,(a2)+
*
add.w d4,d4
and.w d1,d4 * Zweites Byte holen
move.w 0(a3,d4.w),d4
move.w d4,2(a1)
move.w d4,2(a2)
*
dbra d0,ME2_21
*
addq.l #4,a1
addq.l #4,a2
*
dbra d6,ME2_20
*
lea 80(a0),a0 * Zeiger auf die jeweils nächsten
lea 160(a1),a1 * Zeilen setzen
lea 160(a2),a2
*
dbra d7,ME2_10
*
rts
*
*****************************************
*
ZOOM_LOW:
moveq #80,d2 * Low-Res Faktor 2
moveq #50,d3
move.w #319-2*80,d4
moveq #199-2*50,d5
bsr CALC_SCRPTR
*
add.w d1,d1 * Offsets an niedere
lsl.w #2,d0 * Auflösung anpassen
add.w d0,d1 * Offsets auf Adresse des
adda.w d1,a0 * Hintergrundbildes addieren
*
lea 160(a1),a2
lea BUFFER_2,a3
*
moveq #99,d7 * Anzahl Zeilen-1
move.w #$1FE,d1 * Maske
moveq #7,d5 * Konstante
*
LO2_10:
moveq #9,d6 * 10 Words pro Zeile
*
LO2_20:
moveq #3,d0 * 4 Planes bearbeiten
*
LO2_21:
move.w (a0)+,d4 * Sourcedaten holen
swap d4
move.w 6(a0),d4
*
lsl.l d2,d4 * Word zurechtshiften
swap d4 * Zweites Byte in Byte 4
* * Erstes Byte in Byte 3
*
move.w d4,d3 * Erstes Byte holen
lsr.w d5,d3
and.w d1,d3
move.w 0(a3,d3.w),d3
move.w d3,(a1)+
move.w d3,(a2)+
*
add.w d4,d4
and.w d1,d4 * Zweites Byte holen
move.w 0(a3,d4.w),d4
move.w d4,6(a1)
move.w d4,6(a2)
dbra d0,LO2_21 * Nächste Einzelplane bearbeiten
*
addq.l #8,a1
addq.l #8,a2
dbra d6,LO2_20 * Nächste 4 Planes bearbeiten
*
lea 80(a0),a0 * Zeiger auf nächste
lea 160(a1),a1 * Zeilen setzen
lea 160(a2),a2
*
dbra d7,LO2_10
*
rts
*
*****************************************
*
QUICK:
movea.w #$8200,a1 * Hardwareadresse
move.l a0,d0 * Hintergrundbild
*
lsr.l #8,d0
movep.w d0,1(a1) * Bildadresse setzen
*
rts
*
*****************************************
*
CALC_SCRPTR:
movem.w MOUSE_POS(pc),d0-d1 * Mausposition holen
*
sub.w d2,d0 * Linke Lupenkante
bpl.s CHECK_1 * Ok....
moveq #0,d0
*
CHECK_1:
sub.w d3,d1 * Obere Lupenkante
bpl.s CHECK_2 * Ok....
moveq #0,d1
*
CHECK_2:
cmp.w d4,d0 * Rechte Lupenkante
bls.s CHECK_3 * Ok....
move.w d4,d0
*
CHECK_3:
cmp.w d5,d1 * Untere Pixelkante
bls.s CHECK_4 * Ok....
move.w d5,d1
*
CHECK_4:
moveq #15,d2 * Offset in Punkten auf die nächste links
and.w d0,d2 * liegende Wortgrenze
*
lsr.w #4,d0 * Offset in Bytes auf Wortgrenze
add.w d0,d0
mulu #80,d1 * Offset in Bytes/Zeile
rts
*****************************************
*
* In der nächsten Routine wird der Status
* der Tasten abgefragt.
* Entsprechend der Tasten und der Auflösung
* wird daraufhin umgeschaltet.
* Bei Farbe werden die Vektoren neu gesetzt, da
* sie beim Reset' überschrieben werden!
*
GET_MODE:
move.l SHIFT_ADDR(pc),a0 * Shiftstatus
moveq #%01101011,d0 * Wichtige Tasten ausmaskieren
and.b (a0),d0
*
cmpi.b #%1011,d0 * SHIFT-SHIFT-ALTERNATE?
bne.s MODE_END * Nein...
*
tas.b KEY_FLAG * War bereits gedrückt?
bne.s MODE_RTS * Ja... .
*
move.b MODE(pc),d0 * Modus wechseln
subq.b #1,d0 * (Nur möglich falls das Key-Flag
bpl.s SET_MODE * gelöscht war)
*
*gespeicherte Auflösung und Max-Wert für Mode holen
move.w RESOLUTION(pc),d1 * Auflösung
move.b MAXMODE(pc,d1.w),d0 * Maximalwert je Auflösung
*
SET_MODE:
move.b d0,MODE * Neuen Modus setzen
rts * Key-Flag bleibt
*
MODE_END:
clr.b KEY_FLAG * Flag löschen
* * Tasten losgelassen
MODE_RTS:
rts
*
MAXMODE:
dc.b 1 * Max.Modus in niedriger Auflösung
dc.b 1 * Max.Modus in mittlerer Auflösung
dc.b 3 * Max.Modus in hoher Auflösung
dc.b 0 * -
*
*****************************************
*
CONOUT:
movem.l a0-a2/d0-d2,-(sp) * Register retten
*
move.l 28(sp),-(sp) * Textadresse
move.w #PRINT_LINE,-(sp)
trap #GEMDOS
addq.l #6,sp
*
movem.l (sp)+,a0-a2/d0-d2 * Register laden
*
move.l (sp)+,(sp) * Return-Adresse setzen!
rts
*
*****************************************
*
dc.l $02101968 * Magic Number
*
NEW_MOUSE:
movem.l d0-d3/a0-a1,-(sp) * Register retten
*
move.b (a0)+,d0 * Ist es ein Mauspaket?!
cmpi.b #$f8,d0
bcs.s END_MOUSE * Nein...
cmpi.b #$fc,d0
bcc.s END_MOUSE * Nein...
*
move.w #319,d2 * Mausgrenzen je nach Auflösung
move.w #199,d3
move.w RESOLUTION(pc),d0 * Auflösung
beq.s CHK_MOUSE * War niedere
*
move.w #639,d2
subq.w #1,d0
beq.s CHK_MOUSE * War mittlere
*
move.w #399,d3 * War hohe
*
CHK_MOUSE:
move.b (a0)+,d0 * Hole relative Mausbewegung x
move.b (a0)+,d1 * und y
*
lea MOUSE_POS(pc),a1 * Addiere Mausbewegung
ext.w d0 * auf Mauskoordinaten, prüfe
ext.w d1 * auf Bildschirmgrenzen
*
add.w (a1),d0 * xpos := xpos + rx
bpl.s MS_1
moveq #0,d0 * if xpos < 0,xpos = 0 1
*
MS_1:
cmp.w d2,d0 * Abfrage anders
bls.s MS_2
move.w d2,d0 * if xpos > xmax,xpos = xmax
*
MS_2:
add.w 2(a1),d1 * ypos := ypos + ry
bpl.s MS_3
moveq #0,d1 * if ypos < 0 then ypos * 0
*
MS_3:
cmp.w d3,d1 * Abfrage anders
bls.s MS_4
move.w d3,d1 * if ypos > ymax then ypos = ymax
*
MS_4:
move.w d0,(a1)+ * Neue Mausposition sichern
move.w d1,(a1)+
*
END_MOUSE:
movem.l (sp)+,d0-d3/a0-a1 * Register laden
* * und weiter mit altem Mausinterrupt
move.l KBDVBASE+4(pc),-(sp)
rts
*
*****************************************
*
CREATE_TABLE:
lea BUFFER_2,a0 * Tabelle für Faktor 2 berechnen
moveq #0,d0
*
MAKE_20:
move.b d0,d1
moveq #7,d7 * 8 Bits pro Byte
*
MAKE_21:
move.b d1,d3 * Aus einem Byte ein Word machen
add.b d1,d1 * 2 x dasselbe Bit testen u. setzen
addx.w d2,d2
add.b d3,d3
addx.w d2,d2
dbra d7,MAKE_21
*
move.w d2,(a0)+
*
addq.b #1,d0
bne.s MAKE_20
*
*******************************************
*
lea BUFFER_4,a0 * Tabelle berechnen (Faktor 4)
moveq #-16,d4 * Maske Nibble links
moveq #15,d5 * Maske Nibble rechts
moveq #0,d0 * Startbyte
*
MAKE_40:
move.b d0,d1 * Aktuelles Byte in d1 bearbeiten
moveq #3,d7 * 4 mal 2 Bits /Byte
*
MAKE_41:
add.b d1,d1 * Mache aus 2 Bits
scs d2 * ein Byte
and.b d4,d2
add.b d1,d1
scs d3
and.b d5,d3
or.b d2,d3
move.b d3,(a0)+
dbra d7,MAKE_41
*
addq.b #1,d0 * Bis alle 256 Bytes
bne.s MAKE_40 * durchgearbeitet sind
*
*****************************************
*
lea BUFFER_8,a0 * Tabelle für Faktor 8
moveq #0,d0 * Startbyte
*
MAKE_80:
move.b d0,d1 * Aktuelles Byte in d1 bearbeiten
moveq #7,d2 * 8 Bits pro Byte
*
MAKE_81: add.b d1,d1 * aus Bit Byte machen
scs (a0)+
dbra d2,MAKE_81
*
addq.b #1,d0 * Bis alle 256 Bytes
bne.s MAKE_80 * bearbeitet wurden
*
rts
*
*****************************************
*
INSTAL:
moveq #3,d0 * Auflösung sichern
and.b $FFFF8260,d0
move.w d0,RESOLUTION
move.l $70,VBL_VEC * VBL-Vektor umleiten
move.l #NEW_VBL,$70
*
move.l $B8,XBIOS_VEC * XBIOS umleiten
move.l #NEW_XBIOS,$B8
*
*
*
move.l $4F2,d0 * Pointer auf Sysbase
beq.s UNDEF * Falls Null, dann direkt $E1B setzen
* * (Zur Sicherheit falls Adresse = 0)
*
move.l d0,a0
move.l 36(a0),a1 * Adresse des Kbdshift-Status
* * ab Tos-Version 1.2
*
cmpi.w #$0102,2(a0) * Tos-Version älter als 1.2?
bcc.s TOS_12 * Ja...
*
UNDEF:
move.w #$ElB,a1 * Sonst Ram-Adresse direkt setzen
*
TOS_12:
move.l a1,SHIFT_ADDR * Adresse sichern
*
rts
*
*****************************************
*
GRAF_MKSTATE:
lea CTRL_ARRAY,a0
*
move.w #79,(a0)+
clr.w (a0)+
move.w #5,(a0)+
clr.l (a0)
*
subq.l #6,a0
*
move.l #AES_DATA,d1 * graf_mkstate
move.w #$C8,d0
trap #2
*
move.l AES_DATA+12(pc),a0 * int_out
move.l 2(a0),MOUSE_POS * mouse x,y
*
rts
*
*****************************************
*
APP_INIT:
moveq #10,d0
bsr AES_CALL
*
rts
*
*****************************************
*
APP_EXIT: * Abmelden der
moveq #19,d0 * Applikation
bsr AES_CALL * AES aufrufen
*
rts
*
*****************************************
*
AES_CALL:
lea CTRL_ARRAY,a 0
*
move.w d0,(a0)+
clr.w (a0)+
move.w #1,(a0)+
clr.l (a0)
*
subq.l #6,a0
*
move.l #AES_DATA,d1
move.w #$C8,d0
trap #2
*
rts
*
*****************************************
*
PAUSE:
moveq #30,d0 * Warteschleife
*
WAITLOOP:
dbra d1,WAITLOOP * Prozessor beschäftigen
dbra d0,WAITLOOP
*
rts
*
**********************************************
*
* data
*
**********************************************
*
KBDVBASE: dc.l -1 * Vektortabelle-Adresse
dc.l -1 * Gesicherter Maus-Vektor
*
MOUSE_POS: dc.w 639 * Initialdaten Mausposition
dc.w 399
*
FLIPBASE_1: dc.l -1 * Basisadresse Bild 1
FLIPBASE_2: dc.l 32000 * Bild 2- Bild_l+32000
PHYSBASE: dc.l -1 * Hintergrund-Bildschirm
*
XBIOS_VEC: dc.l -1 * XBIOS-Adresse
VBL_VEC: dc.l -1 * VBL-Adresse
SHIFT_ADDR: dc.l -1 * Tastaturstatus-Adresse
*
RESOLUTION: dc.w -1 * Auflösung
COUNTER: dc.w REPEAT * Countdown f.Bildaufbau
*
AES_DATA: dc.l CTRL_ARRAY
dc.l AES_GLOBAL
dc.l CTRL_INT_IN
dc.l CTRL_INT_OUT
dc.l CTRL_ADDR_IN
dc.l CTRL_ADDR_OUT
*
AES_GLOBAL: dc.w 0
dc.w 0
dc.w 0
dc.l $02101968
ds.l 5
*
MODE: dc.b 0 * Vergrösserung aus
KEY_FLAG: dc.b 0 * Flag "Tasten werden gedrückt gehalten"
*
MESSAGE: dc.b $1b,'E',10
dc.b 'R E A L T I M E M A G N I F I E R',10,10,10,13
dc.b ' Written by Alexander Beller',10,10,13
dc.b ' (C) 1988 by IMAGIC Grafik',0
ds.w 0
TEXT_END:
**********************************************
*
bss
*
**********************************************
BSS_START:
BUFFER_2: ds.b 256*2 *
BUFFER_4: ds.b 256*4 * Platz für Tabellen
BUFFER_8: ds.b 256*8
CTRL_ARRAY: ds.w 10
CTRL_INT_IN: ds.w 128
CTRL_INT_OUT: ds.w 128
CTRL_ADDR_IN: ds.l 128
CTRL_ADDR_OUT: ds.l 128
FLIPSCR_1: ds.b 32256 * Bildspeicher 1
FLIPSCR_2: ds.b 32000 * Bildspeicher 2
BSS_END: ds.b 0 * Ende des residenten Teils
ds.b 2048 * Programm-Stack
USER_STACK: ds.b 0
**********************************************
end