← ST-Computer 01 / 1989

ST-Ecke: Grossartig - Eine Echtzeitlupe im Selbstbau

Grundlagen

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