ST-Ecke: Grossartig - Eine Echtzeitlupe im Selbstbau

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…

Zu allererst

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.

Aller guten Dinge sind drei

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:

Nicht so habgierig

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.

A Kind of Magic

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.

Der kleine Unterschied

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).

Startposition

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.

Eine super Sache

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.

ADE

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.

Der XBIOS-TRAP

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.

Mäuschen hab’ acht...

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.

Statussymbole

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

Unter der Lupe

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.

CPU-Schonung

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

Größenwahn

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 Realisierung

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.

Das Erzeugen einer vergrößerten Zeile

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:

Das Geheimnis des schnellen Kopierens

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.

Zwei ist besser als Eins

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.

Das Ding mit der gespaltenen Adresse

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.

XBRA

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



Aus: ST-Computer 01 / 1989, Seite 134

Links

Copyright-Bestimmungen: siehe Über diese Seite