Schnelle LINE-A-Routinen in LPR-Modula

In irgendeinem Artikel habe ich mal gelesen, dass „... Das LPR-Modula gut zum Modula-Lernen geeignet ist, aber zum professionellen Gebrauch nicht taugt“. Meine Meinung dazu ist: „Wer das sagt, der LÜÜCHHT!“. Das möchte ich auch gleich beweisen.

Aber gleich zu Anfang muß ich meine Aussage wieder relativieren: Das LPR-Modula hat einige Schwächen, die allerdings teilweise sogar sinnvoll sind (wie das Importieren von VAL aus SYSTEM, ohne das kein Typecasting möglich ist). Schwerwiegend ist das Fehlen einer einwandfreien Speicherverwaltung; „HEAP“ ist nicht fehlerfrei und bietet bei Standalone-Programmen nur 10000 Bytes zum Allozieren an.

Doch nun wieder zu den positiven Seiten. In Ausgabe 6/90 wird im Artikel „Professionelles Spiele-Design“ der sog. „Mega-Ghost-Trick“ erläutert. Dabei heißt es, „einzig und allein das Megamax-Modula-System erlaubt die Möglichkeit der Modulanbindung“. Denkste! Auch mit dem LPR-Modula kann man solche Tricks verwirklichen; häufig genutzte Objekt-Files können resident gehalten, der Compiler von eigenen Programmen aus aufgerufen werden. Doch bekannt ist, daß dem LPR-Modula zuwenig fehlerfreie Bibliotheken zur Verfügung stehen; so habe ich bis heute kein fehlerfreies STRINGS-Modul in die Fingerbekommen! Um Bibliotheken zu schreiben, muß man sich natürlich im System auskennen - doch wie kann man dies ohne ausreichende Dokumentation? Deshalb werde ich im folgenden einige SYSTEM-Befehle erläutern, die nicht ausreichend dokumentiert sind.

Es handelt sich um die Befehle REG, SETREG, INLINE und CODE (CODE ist standardmäßig vorhanden und wird nicht importiert!). Das Beispiel, das ich mir dafür ausgesucht habe, ist eine Low-Level-Line-A-Bibliothek. Low-Level deshalb, weil Line-A-Anwendungen immer zeitkritisch sind und ich deshalb auf Stapel-Benutzung völlig verzichte! Zur Benutzung der Routinen verweise ich daher auf entsprechende Literatur (z.B. „ATARI ST Profibuch“, dem ich die Datenstruktur komplett entnommen habe). Zu Beginn steht bei uns die Initialisierung. Der Line-A-Code lautet dazu A000H und liefert in Register D0 einen Zeiger auf die Line-A-Variablen, in Al einen auf die Tabelle der System-Zeichensätze und in A2 einen auf die Line-A-Routinen. Die Datenstrukturen für die Line-A-Variablen sind in Abb. 1 erläutert. Für diese drei Zeiger definieren wir erstmal drei globale Variablen im Definitions-Modul.

Doch wo und wie belegen wir diese? Im Implementations-Hauptprogramm-Teil, da diese Variablen nur einmal initialisiert werden müssen. Aber wie? Wir benutzen dazu den INLINE-Befehl. Definiert ist er so:

INLINE (w1, w2, w3, ... wn
: WORD); (* w1..wn
           Konstanten *)

Die w1 bis wn werden einfach im Objekt-File als Maschinencode eingefügt, sind also wie geschaffen für unser Problem:

IMPLEMENTATION MODULE 
LowLevelLineA;
.
.
.
BEGIN
    INLINE (0A000H);
    .
    .
    .

Doch wie erhalten wir die Rückgabewerte? Dazu dient uns REG. Definiert ist diese Funktion so:

REG (regno : [0..15]) :
LONGINT;

0..15 gibt dabei die Registernummer an, D0 = 0, D1 = 1,... A0 = 8 usw. Also geht unser Programm so weiter:

    .
    .
    .
    LineAVars    := REG(0);
    SysFontTable := REG(9); 
    LineARoutines:= REG(10); 
END LowLevelLineA.

Wenn man nun diese drei globalen Variablen in ein anderes Programm importiert, hat man volle Kontrolle über die Line-A-Variablen. Doch weiter zu den Routinen. Diese haben die Funktionsnummern A001H bis A00FH. Wir könnten für jede dieser Routinen eine Prozedur schreiben, etwa in der Form:

PROCEDURE PutPixel;
BEGIN
    INLINE (0A001H);
END PutPixel;

Doch - wie teuer ist das! Für jeden Prozeduraufruf wird im Modula-System der Stapel umorganisiert und einiges mehr gemacht (Register gerettet usw.). Dutzende von Maschinenbefehlen - nur um einen einzigen auszuführen! Glücklicherweise stellt uns dazu das System den CODE-Befehl zur Verfügung. Form:

PROCEDURE ....; CODE (w : WORD); (* w Konstante *)

Wenn man eine Prozedur so definiert, wird bei jedem Prozedur-Aufruf in einem Objekt-Programm nur der entsprechende Wert w als Maschinencode eingefügt. Das Modula-System selbst benutzt diesen Befehl im Process-Modul (für Trap 3 und 4). Wenn man den CODE-Befehl in einem Definitions-Modul benutzt, darf diese Prozedur im Implementations-Modul nicht mehr definiert werden.

Damit können wir unsere weiteren Line-A-Prozeduren definieren. Hier sei nur noch am Beispiel PutPixel die Vorgehensweise erläutert, das vollständige Modul ist als Listing abgedruckt (Listing 1). Der Maschinencode für PutPixel lautet A001H. Also definieren wir PutPixel wie folgt:

DEFINITION MODULE 
LowLevelLineA;
.
.
.
PROCEDURE PutPixel;
CODE (0A001H);
.
.
.
END LowLevelLineA.

Die so definierten Prozeduren können wir wie ganz normal definierte benutzen. Siehe dazu Beispiel 1, daß den Bildschirm mit Punkten füllt.

Wer jetzt darüber meckert, daß die Line-A-Variablen per Hand gesetzt werden müssen, kann sich ja eine High-Level-Bibliothek schreiben. Ich halte das allerdings nicht für sinnvoll, denn wer soviel Zeit hat, daß er Dutzende von Werten auf einen Stack schreibt und wieder herunterholt, der sollte auf die entsprechenden VDI-Routinen zurückgreifen, die erheblich komfortabler sind und dazu (eigentlich) sogar systemunabhängig.

Zum Schluß noch die Frage, was man noch mit dem CODE-Befehl machen kann. Antwort: noch viel, viel mehr! Ansatzweise könnte man sogar einen Mini-Assembler daraus entwickeln (siehe Bild 2).

Nun noch viel Erfolg beim „professionellen“ Programmieren mit dem LPR-Modula. Wer Fragen dazu hat, erreicht mich per E-Mail unter J_KRINKE@BDB.ZER.

DEFINITION MODULE LowLevelLineA;
TYPE LINEA = RECORD ...
.
.
.
VAR LineAVars     : POINTER TO LINEA;
    SysFontTable  : POINTER TO ARRAY[0..2] OF ADDRESS; 
    LineARoutines : POINTER TO ARRAY[0..14] OF ADDRESS;
.
.
.
END LowLevelLineA.

Bild 1: So werden die drei globalen Variablen definiert

PROCEDURE trap11;    CODE(474BH);
PROCEDURE trap13;    CODE(474DH);
PROCEDURE nop;       CODE(4E71H);
PROCEDURE clrlD1;    CODE(4281H); (* clr.l d1 *)
PROCEDURE sublD0D1;  CODE(9280H); (* sub.1 d0,d1 *)
PROCEDURE trapl;     CODE(4741H);
PROCEDURE moveql5D0; CODE(7005H); (* moveq.l #5,d0 *) 
PROCEDURE movelD0D1; CODE(220OH); (* move.l d0,d1  *)

Bild 2

IMPLEMENTATION MODULE LowLevelLineA;

FROM SYSTEM IMPORT VAL, REG, INLINE, ADDRESS;

CONST RegD0 =  0;
      RegA1 =  9;
      RegA2 = 10;

BEGIN
    INLINE (0A000H);
    LineAVars     := VAL(ADDRESS,REG(RegD0));
    SysFontTable  := VAL(ADDRESS,REG(RegA1)); 
    LineARoutines := VAL(ADDRESS,REG(RegA2)); 
END LowLevelLineA.

Listing 1

MODULE LADemo;

FROM LowLevelLineA IMPORT LineAVars, PutPixel, ArbitraryLine,
     FilledRectangle, FilledPolygon;

FROM SYSTEM IMPORT ADR;

CONST videowidth  = 640; (*fuer hohe Aufloesung*) 
      videoheigth = 400;

PROCEDURE Rectangle;
VAR Pattern : ARRAY[0..0] OF CARDINAL;
BEGIN
    Pattern[0] := 0;
    LineAVars^.X1 := 0;     (*gesamter Bildschirm*)
    LineAVars^.Y1 := 0;
    LineAVars^.X2 := videowidth-1;
    LineAVars^.Y2 := videoheigth-1;
    LineAVars^.COLBIT0 := 1;        (*alle Planes*)
    LineAVars^.COLBIT1 := 1;
    LineAVars^.COLBIT2 := 1;
    LineAVars^.COLBIT3 := 1;
    LineAVars^.WMODE := 0;          (*Replace*)
    LineAVars^.PATPTR := ADR(Pattern[0]);
    LineAVars^.PATMSK := 0; (*1 Linienmuster*)
    LineAVars^.MFILL := 0;  (*nur erste Ebene*) 
    LineAVars^.CLIP := 0;   (*Clipping aus*) 
    FilledRectangle;
END Rectangle;

PROCEDURE Points;
VAR x,y,i : INTEGER;
BEGIN
    LineAVars^.INTINA[0] := 1; (* Farbe setzen *)
    FOR i := 1 TO 32767 DO
        x := ((i MOD 211) * 7) MOD videowidth + i MOD 7;
        y := ((i MOD 83) * 5) MOD videoheigth + i MOD 5;
        LineAVars^.PTSINA[0] := x;
        LineAVars^.PTSINA[1] := y;
        PutPixel;
    END(*FOR*);
END Points;

PROCEDURE Lines;
VAR i : INTEGER;
BEGIN
    LineAVars^.COLBIT0 := 1;
    LineAVars^.COLBIT1 := 1;
    LineAVars^.COLBIT2 := 1;
    LineAVars^.COLBIT3 := 1;
    LineAVars^.LNMASK  := 0FFFFH;
    LineAVars^.WMODE   := 0; (* Replace *)
    LineAVars^.LSTLIN  := 1; (* letzten Punkt Zeichen *)
    FOR i := 0 TO videowidth-1 BY 8 DO 
        LineAVars^.X1 := videowidth-i-1; 
        LineAVars^.Y1 := 0;
        LineAVars^.X2 := 0;
        LineAVars^.Y2 := i DIV 2;
        ArbitraryLine;
    END(*FOR*);
END Lines;

PROCEDURE Polygon;
VAR Pattern : ARRAY[0..15] OF CARDINAL;
    y : INTEGER;
BEGIN
    Pattern[0] := 0FFFFH; (*Muster*)
    Pattern[1] := 0EEEEH;
    Pattern[2] := 0DDDDH;
    Pattern[3] := 0CCCCH;
    Pattern[4] := 0BBBBH;
    Pattern[5] := 0AAAAH;
    Pattern[6] := 09999H;
    Pattern[7] := 08888H;
    Pattern[8] := 07777H;
    Pattern[9] := 06666H;
    Pattern[10]:= 05555H;
    Pattern[11]:= 04444H;
    Pattern[12]:= 03333H;
    Pattern[13]:= 02222H;
    Pattern[14]:= 01111H;
    Pattern[15]:= 0H;
    LineAVars^.PTSIN^[0] := 10;
    LineAVars^.PTSIN^[1] := 10;
    LineAVars^.PTSIN^[2] := 160;
    LineAVars^.PTSIN^[3] := 40; 
    LineAVars^.PTSIN^[4] := 170;
    LineAVars^.PTSIN^[5] := 90; 
    LineAVars^.PTSIN^[6] := 310;
    LineAVars^.PTSIN^[7] := 120;
    LineAVars^.PTSIN^[8] := 180;
    LineAVars^.PTSIN^[9] := 190;
    LineAVars^.PTSIN^[10] := 10;
    LineAVars^.PTSIN^[11] := 10;
    LineAVars^.CONTRLA[1] := 6;
    LineAVars^.COLBIT0 := 1;
    LineAVars^.COLBIT1 := 1;
    LineAVars^.COLBIT2 := 1;
    LineAVars^.C0LBIT3 := 1;
    LineAVars^.WMODE  := 0; (* Replace *)
    LineAVars^.PATPTR := ADR(Pattern);
    LineAVars^.PATMSK := 15;(* 16 Linienmuster *)
    LineAVars^.MFILL  := 0; (* nur erste Ebene *)
    LineAVars^.CLIP   := 0; (* Clipping aus *)
    FOR y := 10 TO 190 DO 
        LineAVars^.Y1 := y;
        FilledPolygon 
    END(*FOR*);
END Polygon;

BEGIN
    Rectangle;
    Points;
    Lines;
    Polygon;
END LADemo.

Jens Krinke
Aus: ST-Computer 12 / 1990, Seite 96

Links

Copyright-Bestimmungen: siehe Über diese Seite