Graf3D
Volume Number: 4
Issue Number: 6
Column Tag: Forth Forum
Graf3D Library Access 
By Jörg Langowski, MacTutor Editorial Board
Graf3D and Mach2 - accessing external libraries
The subject of this month has been initiated by one of our readers, Paul Thomas,
who in a desperate mood sent me both a paper letter and GEnie mail, because he needed
to know whether it would be possible at all to access the Graf3D routines from Mach2.
A little background for those who weren’t with the Mac (or with us, for that
matter) from the beginning: Graf3D is a set of routines on top of QuickDraw, which
enable to create a 3-d GrafPort with all necessary support to do 3-dimensional
perspective drawing. The routines aren’t explained in Inside Macintosh, but in several
other different documents, one of them being the MPW Pascal manual (Appendix J).
Graf3D was also part of the Lisa Workshop, and one of the first Macintosh demo
programs, Boxes, used those routines. The Boxes program - many of you might have
seen it - draws at random fifteen rectangular boxes on a plane and displays them in a
3-d perspective view. The program is written originally in Lisa Pascal, and also
contained on the TML Pascal disk as an example. Listing 4 shows the original program.
Glancing through the code, you might notice that this example certainly wouldn’t
please the User Interface Thought Police today. Using the whole screen as a GrafPort
without regard to anything else on there is a definite no-no in the days of Multi finder,
and if you run the example under Multi finder, you won’t find your windows back on the
screen afterwards because they are all covered by little boxes. Nevertheless, it is a
nice example for the use of Graf3D, and we’ll show you this month how to translate it
into Mach2 (We’ll also correct the garbled screen problem under Multi finder).
First and most difficult question: How can we access routines from Forth that are
clearly labeled (Not in ROM) in the documentation? This remark tells us, of course,
that they are part of some object library, no source code available and inaccessible to
the Forth user. Just like the printing manager was before it was implemented in the
Mach2 and MacForth systems.
There may be other libraries to come, also not in ROM, and waiting for updates
takes a long time. So I’ll give you two general strategies to get access to object library
code from Forth, making you independent of those updates.
The first scheme I thought up was simply to write a short Pascal program that
calls all the routines at least once - so that they will be loaded -, compile that
program, create a linker map if possible, and pass the final application through Nosy.
That will get you a more or less annotated assembly listing from which you can extract
the source code of the not in ROM routines that you are interested in.
Well, that works for you and me on our respective desks, but we certainly cannot
publish reverse-engineered Apple source code in MacTutor, at least if we want to stay
in business. Also, it is a lot of work (i.e. translating to a different assembler dialect,
writing the glue code, etc.), and all in all qualifies as a master example of a dirty hack.
There is an easier way to go, and much more general, too: write some sort of a
linker for Pascal library code. We write an assembly main program (Listing 2),
which is just a table of JMP instructions to the routines that we want to call, and let
the MPW linker do its job (listing 3).
The assembly/link script will create a new resource, gr3D, which contains the
jump instructions at the beginning, followed by the linked Graf3D code. All we have to
do now is to provide a block of memory in our Forth code where this resource can be
copied to.
In order to assign Forth words to the JMP instructions, we simply make a table
of CREATEs, which will allocate 4 bytes to each name (the length of a JMP d(PC)
instruction), and create a dictionary entry so that we know the routine’s address in the
jump table. After the block of CREATEs, we allocate sufficient memory so that the gr3D
resource can be loaded (see listing 1).
We write a word, Init3D, that gets the gr3D resource and copies it into the table,
starting with the address of the first Graf3d word, gInitGrf3D. Now the Graf3D
routines are all set up for use by Mach2. As simple as that.
Well, not quite. We still need to write glue code to move the parameters from the
Forth stack (A6) to the Pascal stack (A7). That glue code is rather simple and is just
the same as is used for calling toolbox ROM routines. Like the traps, the Graf3D code
preserves all registers that are vital to Mach2 (A2-A7, D4-D7). Like in the case of
traps, we have to make A7 point to the lowest stack (EXG D4,A7) in order to avoid
overwriting of Mach2 space by the intermediate Quickdraw record that is put between
the stack and the heap. The glue code for the different Graf3D routines is given at the
beginning of listing 1.
The Boxes examples should be self-explanatory when you compare it with the
Pascal code; in fact, it might help some of you Pascal programmers and casual Forth
readers appreciate that there is really no big difference between Forth and Pascal
code...
I should point out some more things. First, the ‘access words’ for fields in a
record, .x, .y, .pt1, and so on, defined at the beginning of the Forth code. Second, the
word restore.screen, which cleans up the display after the example has run; very
important when running under Multi finder. It basically does a
PaintBehind(FrontWindow, grayRgn), the same mechanism by which many screen
savers restore a blacked-out screen. Third, we define a terminal task that runs the
Boxes example; that way, we can simply check for ?terminal after each drawing loop
to see whether we’re done. Again, we have to make sure (like always in Mach2
multitasking) that the correct GrafPort is set by calling myPort3D SetPort3D before
each passage through the loop. This is because any PAUSE-containing word, such as
?terminal, may change the active GrafPort.
For using the example, you will first have to create the Graf3Dglue resource file
with the gr3D resource in it, then move that resource into the MACH.RSRC file before
starting your Mach2 system. In case you don’t have access to MPW, the source code
disk contains the Graf3Dglue and MACH.RSRC files. The complete program, of course,
is also on the disk.
That’s it for this month; next month I plan to discuss various projects of
object-oriented extensions to Mach2 that have recently appeared on the networks.
Some of you might also appreciate that NEON hasn’t died altogether, but that there is at
least one person trying to extend it in a very interesting way.
Listing 1: The Boxes example in Mach2
\ Graf3d / Mach2 glue code
\ An example for calling MPW routines from Forth
\ J. Langowski April 1988
\ __________________________________________
Only Forth Also Mac Also Assembler
\ some general definitions first
16 CONSTANT portRect
GLOBAL
CODE SCALE
MOVE.L (A6)+,D0
BMI.S @1
MOVE.L (A6),D1
ASL.L D0,D1
MOVE.L D1,(A6)
RTS
@1 MOVE.L (A6),D1
NEG.L D0
ASR.L D0,D1
MOVE.L D1,(A6)
RTS
END-CODE
global
CODE white
MOVE.L (A5),-(A6)
SUBQ.L #8,(A6)
RTS
END-CODE MACH
global
CODE black
MOVE.L (A5),-(A6)
SUBI.L #16,(A6)
RTS
END-CODE MACH
global
CODE gray
MOVE.L (A5),-(A6)
SUBI.L #24,(A6)
RTS
END-CODE MACH
: 4ASCII
0
4 0 DO
8 SCALE 0 WORD 1+ C@ +
LOOP
;
4ASCII gr3D CONSTANT “gr3D \ resource ID
\ Graf3D jump table
CREATE gInitGrf3D
CREATE gOpen3DPort
CREATE gSetPort3D
CREATE gGetPort3D
CREATE gMoveTo2D
CREATE gMoveTo3D
CREATE gLineTo2D
CREATE gLineTo3D
CREATE gMove2D
CREATE gMove3D
CREATE gLine2D
CREATE gLine3D
CREATE gViewPort
CREATE gLookAt
CREATE gViewAngle
CREATE gIdentity
CREATE gScale
CREATE gTranslate
CREATE gPitch
CREATE gYaw
CREATE gRoll
CREATE gSkew
CREATE gTransform
CREATE gClip3D
CREATE gSetPt3D
CREATE gSetPt2D
\ The glue code is 2636 bytes long, so we allocate
\ sufficient additional buffer space to put it into
2600 ALLOT
: Init3D \ gets Graf3D code from gr3D=1 resource
\ and copies it into buffer
“gr3D 1 call GetResource
dup @ swap call SizeRsrc
[‘] gInitGrf3D swap cmove
;
CODE InitGrf3d ( globalPtr -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gInitGrf3d
EXG D4,A7
RTS
END-CODE
CODE Open3DPort ( port -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gOpen3DPort
EXG D4,A7
RTS
END-CODE
CODE SetPort3d ( port -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gSetPort3d
EXG D4,A7
RTS
END-CODE
CODE GetPort3d ( VAR port -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gGetPort3d
EXG D4,A7
RTS
END-CODE
CODE MoveTo2d ( x y -- )
EXG D4,A7
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #8,A6
JSR gMoveTo2d
EXG D4,A7
RTS
END-CODE
CODE MoveTo3d ( x y z -- )
EXG D4,A7
MOVE.L 8(A6),-(A7)
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #12,A6
JSR gMoveTo3d
EXG D4,A7
RTS
END-CODE
CODE LineTo2d ( x y -- )
EXG D4,A7
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #8,A6
JSR gLineTo2d
EXG D4,A7
RTS
END-CODE
CODE LineTo3d ( x y z -- )
EXG D4,A7
MOVE.L 8(A6),-(A7)
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #12,A6
JSR gLineTo3d
EXG D4,A7
RTS
END-CODE
CODE Move2d ( dx dy -- )
EXG D4,A7
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #8,A6
JSR gMove2d
EXG D4,A7
RTS
END-CODE
CODE Move3d ( dx dy dz -- )
EXG D4,A7