I don't want to start a compiler-war in the assembler programmer's journal,
but I do want to show some nice in-line assembly routines for FST Modula-2.
FST (or Fitted Software Tools) was a shareware Modula-2 compile made by Roger
Carvalho. He eventualy gave up the concept of shareware and made his final
version freeware. If you look carefully you can find this package in many
software repositories like Simtel. Also the FreeDOS website used to harbor
this final version.
For this Modula-2 compiler I used my VGA routines (see previous issues) and
some in-line assembly to give this compiler a way to do graphics modes.
I uploaded the full sources to SimTel some months (or years?) ago, so if you
would like to have a detailed look at it, go there and look for it.
Modula-2 is despised by many, but it is the most structured language ever
made. And that's also probably the reason why most coders refuse to use it.
You must follow the compiler, whatever you do. A high price, but the result is
that Modula-2 programs seldomly crash. They can bail-out in the middle of the
program, but they will not hang due to a pointer or indexing error.
Anyway, here's my addition to this marvelous language:
IMPLEMENTATION MODULE VgaLib;
PROCEDURE SHL (x, y : CARDINAL) : CARDINAL; (* Shift left x, y bits. *)
VAR result : CARDINAL;
BEGIN
ASM
MOV AX, x
MOV CX, y
AND CX, 15 (* Mask off lower nybble *)
JCXZ ok (* Get out if no shift. *)
SHL AX, CL
ok: MOV result, AX (* Store result. *)
END;
RETURN result;
END SHL;
The only "drawback" is that the in-line code must be 8088 style. So you won't
be eable to use MMX instructions, but almost no-one ever needs those.
FST Modula-2 offers direct access to (values of) variables. Neat. Makes the
in-line feature very convenient to use.
PROCEDURE SetColour (Colour : CHAR); (* Define colour to work with. *)
BEGIN
ASM
MOV DX, 03C4H (* VGA controller port *)
MOV AH, Colour
MOV AL, 2
OUT DX, AX
END;
END SetColour;
Compare the following routine with the one I entered for the VGA-12h code in
A86 assembly language format. There's some Modula-2 overhead, but the actual
plotting is done in ASM, for speed-reasons.
PROCEDURE Plot (VAR InWin : WinData); (* Plot point on CurX, CurY. *)
VAR x, y : CARDINAL;
BEGIN
x := InWin.CurX + InWin.TopX;
y := InWin.CurY + InWin.TopY;
ASM
MOV AX, 0A000H
MOV ES, AX (* Set up segment register *)
MOV CX, x
AND CX, 7 (* Which bit to plot? *)
MOV AH, 80H
SHR AH, CL (* Compose plotting mask *)
MOV AL, 8
MOV DX, 03CEH
OUT DX, AX (* Set plottingmask *)
MOV AX, y (* Calculate offset in Video RAM *)
MOV BX, AX
ADD AX, AX
ADD AX, AX
ADD AX, BX (* AX := 5 * Y *)
MOV CL, 4
SHL AX, CL (* AX := 16 * 5 * Y *)
MOV BX, x
SHR BX, 1
SHR BX, 1
SHR BX, 1
ADD BX, AX (* plus X / 8 *)
MOV AL, ES:[BX]
MOV AL, 0FFH
MOV ES:[BX], AL (* and plot it *)
END;
END Plot;
PROCEDURE DrawH (VAR InWin : WinData; Flag : BOOLEAN);
(* Draw a horizontal line from CurX, CurY for DeltaX pixels. *)
VAR Index, Stop, x, dx, y, Kval : CARDINAL;
Emask, Lmask, Val : CHAR;
BEGIN
IF Flag THEN (* Flag = TRUE => Plot, else UnPlot *)
Val := 0FFX;
ELSE
Val := 0X;
END;
IF InWin.DeltaX < 18 THEN
FOR Index := 0 TO InWin.DeltaX DO (* For short lines *)
Plot (InWin);
INC (InWin.CurX);
END;
ELSE
x := InWin.TopX + InWin.CurX; (* For long lines *)
y := InWin.TopY + InWin.CurY;
dx := InWin.DeltaX;
ASM
MOV AX, 0A000H
MOV ES, AX (* Set up segment register *)
MOV CX, x
AND CX, 7
MOV BX, 8
SUB BX, CX
MOV AL, 0FFH
SHR AL, CL
MOV Emask, AL (* compose plotting mask *)
MOV CX, dx
SUB CX, BX
MOV AX, CX
AND AX, 7
PUSH AX (* Save L-val *)
SUB CX, AX
SHR CX, 1
SHR CX, 1
SHR CX, 1
MOV Kval, CX
MOV AL, 0
POP CX (* retrieve L-val *)
JCXZ L0
MOV AL, 080H
L0: DEC CX
SAR AL, CL
MOV Lmask, AL
MOV AX, y (* Calculate offset in Video RAM *)
MOV BX, AX
ADD AX, AX
ADD AX, AX
ADD AX, BX (* AX := 5 * Y *)
MOV CL, 4
SHL AX, CL (* AX := 16 * 5 * Y *)
MOV BX, x
SHR BX, 1
SHR BX, 1
SHR BX, 1
ADD BX, AX (* plus X / 8 *)
MOV AH, Emask
MOV DX, 03CEH
MOV AL, 8
OUT DX, AX (* Set plotting mask *)
MOV AL, Val
MOV AH, ES:[BX]
MOV ES:[BX], AL (* Do the plotting ... *)
INC BX
MOV CX, Kval
JCXZ L2
MOV AX, 0FF08H
OUT DX, AX
MOV AH, Val
L1: MOV AL, ES:[BX]
MOV ES:[BX], AH
INC BX
LOOP L1
L2: MOV AH, Lmask
MOV AL, 8
OUT DX, AX
MOV AL, ES:[BX]
MOV AL, Val
MOV ES:[BX], AL
END;
INC (InWin.CurX, dx);
END;
END DrawH;
PROCEDURE PlotChar (VAR InWin : WinData; Letter : CHAR);
(* Plot character on InWin.(CurX,CurY). *)
VAR xpos, ypos, MapOfs, VGApos, VGAseg, Pmask : CARDINAL;
Cval : CHAR;
BEGIN
IF Letter = 0AX THEN
INC (InWin.CurY, 16); (* Process LF *)
RETURN;
END;
IF Letter = 0DX THEN
InWin.CurX := InWin.Indent; (* Process CR *)
RETURN;
END;
IF InWin.CurX >= InWin.Width - ChrWid THEN
InWin.CurX := InWin.Indent;
INC (InWin.CurY, 16);
END;
xpos := InWin.CurX + InWin.TopX;
ypos := InWin.CurY + InWin.TopY;
VGApos := 80 * ypos + SHR (xpos, 3);
VGAseg := 0A000H;
MapOfs := ORD (Letter) * 16;
ASM
PUSH ES (* save ES *)
MOV CX, xpos
AND CX, 7
MOV Cval, CL (* nr of bits "off center" *)
MOV BX, 0FF00H
SHR BX, CL
MOV Pmask, BX (* mask to use for left and right halves *)
MOV AX, BX
MOV AL, 8
MOV DX, 03CEH
OUT DX, AX (* set plotting mask for left part *)
MOV CX, 16
MOV BX, VGApos
LES SI, BitMap (* here are the pixels that make the tokens *)
ADD SI, MapOfs
L0: PUSH CX
LES AX, BitMap (* load ES, AX is just scrap *)
MOV AH, ES:[SI] (* load pattern *)
MOV CL, Cval
SHR AX, CL (* compose left half *)
MOV ES, VGAseg
MOV AL, ES:[BX]
MOV ES:[BX], AH (* and "print" it *)
ADD BX, 80 (* point to next row *)
INC SI (* and next pixel pattern *)
POP CX
LOOP L0 (* repeat until done *)
MOV AX, Pmask
CMP AL, 0 (* if Cval = 0 => perfect allignment *)
JE ex (* skip second half *)
XCHG AH, AL (* else repeat the story once more *)
MOV AL, 8
OUT DX, AX (* set up mask for right half *)
MOV CX, 16
SUB BX, 1279 (* 16 x 80 - 1 *)
SUB SI, CX
L1: PUSH CX
LES AX, BitMap
MOV AH, ES:[SI]
MOV AL, 0
MOV CL, Cval
SHR AX, CL
MOV ES, VGAseg
MOV AH, ES:[BX]
MOV ES:[BX], AL
ADD BX, 80
INC SI
POP CX
LOOP L1
ex: POP ES
END;
INC (InWin.CurX, ChrWid); (* point to next printing position *)
END PlotChar;
And here is the promised solution for the "make a box-drawing routine" problem
of the previous issue. OK, the solution is in Modula-2, but since this is such
a clear to understand language it will be no big deal to port this code to
assembly language format.
PROCEDURE MakeBox (InWin : WinData);
(* Make a box on screen starting at (TopX, TopY). *)
BEGIN
InWin.CurX := 0;
InWin.CurY := 0; (* Make sure pointers are correct *)
InWin.DeltaX := InWin.Width - 1;
InWin.DeltaY := InWin.Height - 1; (* setup parameters for drawing lines *)
SetColour (InWin.BoxCol);
DrawH (InWin, TRUE); (* draw horizontal line *)
DrawV (InWin); (* draw vertical line *)
InWin.CurX := 0;
InWin.CurY := 1; (* adjust coordinates *)
DrawV (InWin); (* draw last vertical line *)
DEC (InWin.CurY);
INC (InWin.CurX); (* adjust coordinates once more *)
DrawH (InWin, TRUE); (* draw final line *)
END MakeBox;
END VgaLib.
|