Machine Code Graphics - Part 1 (48S/SX & 48G/GX)

Mark Power #251

This series of articles will deal with graphics on the 48 series and also 
provide examples of machine code. All of the code presented will be in 
Jazz format. Jazz is available on Goodies Disk 10 or from the excellent 
HP48 Software Archive (). The source code, executable functions and a 
library of all of the commands which I will present are available from the 
HPCC web site (http://www.hpcc.org) in case you dont want to type 
them in by hand. I can also email the files to you if you send a request to 
me at "power@gfms.bt.co.uk".

The first function which I will present is the most basic of graphics 
drawing primitives: it simply turns a pixel on. Why do you want a routine 
which is built into the ROM you ask? The answer is that this version can 
be used to turn the pixel on in any GROB, not just in PICT. The routine is 
also the building block of routines in future articles which deal with fast 
line and circle drawing.

Before we look at the machine code, youll need to understand the format 
of a GROB. All objects in the 48 have a 5 nibble prolog, for a GROB this 
prolog is called DOGROB. It is followed the length of the object (again 
stored in 5 nibbles), the pixel height of the GROB and the PIXEL width 
of the GROB (both 5 nibbles). The GROB pixel data then follows in row 
order. The rows must have even byte widths (even though everything else 
is done in nibbles). The most significant bit in a nibble represents the 
rightmost pixel - i.e. the opposite to what you would expect.

Here is an example:

GROB 3 3 502010 is a small y shape

In memory it would be represented as:

DOGROB #00015h #00003h #00003h #502010h

The size in nibbles of the object is:

	20 + GrobHeight*CEIL(GrobWidth/8)*2

In the case above this gives 26 nibbles.
To turn a particular pixel on you need to work out which nibble is 
affected and turn on a particular bit in that nibble. The nibble to be 
modified is given by the equations:

RowOffset = 2*(FLOOR((GrobWidth-1)/8)+1)

NibbleNo = FLOOR(Xcoord/4) + Ycoord * RowOffset

The bit to be modified is given by:

BitMask = 2 ^ (Xcoord MOD 4)

So in a GROB which is 22 pixels by 22 pixels to turn on pixel {#5,#2}:

RowOffset = 6

NibbleNo = 1 + 2 * 6 = 13

BitMask = 2 ^ 1 = 2

So you access the GROB body, move to the 13th nibble and logically OR 
in the bit mask of #0010b. (Remember that pixel co-ordinates in GROBs 
start with {#0,#0} in the top left hand corner).

Enough of the theory, here is the code:


%%HP: T(3)A(D)F(.);
"* PIXONG	V5	140.5 bytes #DFDEh
*
* Turns a pixel on inside a grob.
*
* grob {#x #y}		->	grob
*

::
  0LASTOWDOB! CK2NOLASTWD
  CK&DISPATCH1 #C5
  ::
    2HXSLIST?

    CODE

	GOSBVL	=POP2#	* POP THE COORDINATES
	CR0EX		* SAVE X & Y
	AR1EX
	A=DAT1	A	* GET GROB ADDRESS
	GOSBVL	=SAVPTR	* LEAVE GROB ON STACK FOR USER
	D1=A		* PUT GROB ADDRESS INTO D1
	CR0EX		* RECOVER X & Y
	AR1EX

* From here on, this machine code can be used as a
* subroutine for a much larger program.
*
* IN: D1 = GROB ADDRESS
*     A[A] = X COORDINATE
*     C[A] = Y COORDINATE
*
* OUT: GROB HAS PIXEL (X,Y) TURNED ON
*
* USES: A[W],B[W],C[W],D1,R0
*

	B=0	W
	B=C	A	* SAVE Y IN B[A]
	D1=D1+	10	* D1 IS AT GROB HEIGHT
	C=DAT1	A	* C[A] = GROB HEIGHT
	D1=D1+	5	* D1 NOW AT GROB WIDTH
	?B<C	A	* IF Y >= HEIGHT THEN EXIT
	GOYES	YOK	* ELSE GO AND CHECK X
BOUNDS	GOVLNG	=GETPTRLOOP	* RESTORE RPL REGISTERS

* REPLACE GOVLNG WITH RTN
* IF USING THIS CODE AS A
* SUBROUTINE

YOK	C=0	W
	C=DAT1	A	* C[A] = GROB HEIGTH
	D1=D1+	5	* D1 IS AT 1ST NIBBLE OF GROB BODY
	?A>=C	A	* IF X >= WIDTH THEN EXIT
	GOYES	BOUNDS

* B[A]=Y COORDINATE
* C[A]=GROB WIDTH

	R0=A.F	A	* R0[A]=A[A]=X COORDINATE
	ASRB.F	A	* WORK OUT X DIV 4
	ASRB.F	A	* (THE OFFSET INTO GROB BASED ON X)
	AR0EX.F	A	* R0[A]=X DIV 4, A[A]=X
	ASRC		* WORK OUT X MOD 4
	A=A+A	S
	A=A+A	S
	A=0	A
	ASLC
	ASRB.F	B
	ASRB.F	B	* A[A]=X MOD 4 = NIBBLE MASK VALUE

* Now work out nibble mask from the mask value. The nibble
* mask will be ORed with the existing nibble in the grob to
* turn a pixel on.
*
* If value = 0, mask = 1
* If value = 1, mask = 2
* If value = 2, mask = 4
* If value = 3, mask = 8

	A=0	S	* A[S] WILL HOLD NIBBLE MASK
	A=A+1	S	* START WITH MASK = 1
MASKL	A=A-1	A	* IF VALUE = 0
	GOC	MASKD	* WE'VE GOT RIGHT VALUE
	A=A+A	S	* ELSE MULTIPLY MASK BY 2
	GOTO	MASKL	* AND GO AROUND LOOP AGAIN
*
* A[S]=NIBBLE MASK
* R0[A]=X PIXEL NO
* B[W]=Y PIXEL NO
* C[A]=GROB WIDTH
* D1=@GROB BODY

MASKD	C=C-1	A	* CALCULATE NIBBLE OFFSET INTO
	CSRB.F	A	*  GROB BASED ON Y COORDINATE
	CSRB.F	A
	CSRB.F	A
	C=C+1	A
	C=C+C	A	* C[A]=ROW OFFSET
	ABEX	W	* SAVE MASK IN B[S] AND GET Y INTO A[A]

	B=C	A	* MODIFICATION OF =MPY
	C=0	A	* MULTIPLIES C[A] BY A[A]
MULL	SB=0		* RATHER THAN C[W] BY C[W]
	ASRB.F	A	* THIS SAVES A LOT OF TIME
	?SB=0
	GOYES	MULD
	C=C+B	A
MULD	B=B+B	A
	?A#0	A
	GOYES	MULL	* RESULT IN C[A]

	A=R0.F	A	* A[A]=X NIBBLE NO
	A=A+C	A	* A[A]=NIBBLE OFFSET INTO BODY
	CD1EX		* C[A]=GROB BODY START
	C=C+A	A	* C[A]=NIBBLE TO MASK
	CD1EX		* POINT AT NIBBLE USING D1
	C=DAT1	S	* GET OLD PIXEL
	C=C!B	S	* NEW = OLD OR MASK
	DAT1=C	S	* WRITE NEW PIXEL INTO GROB

WAYOUT	GOVLNG	=GETPTRLOOP	* RESTORE RPL REGISTERS

* REPLACE GOVLNG WITH RTN
* IF USING THIS CODE AS A
* SUBROUTINE

    ENDCODE
  ;
;"

Hopefully the comments will give you an idea of what is happening, 
working through an example should make it very clear.

If you are unfamiliar with 48 machine code, Id recommend you get hold 
of "An Introduction to HP48 System RPL and Assembly Language 
Programming" by James Donnelly (details in the 1997/8 Memberpack).

When you use this routine a couple of useful SYS RPL addresses are:

ABUFF 	#12655h		Returns the stack GROB
GBUFF	#12665h		Returns the PICT GROB

Assuming that you have called the assembled routine GRPIXON:

 #12665h SYSEVAL {#0h #0h} GRPIXON

will turn on the top leftmost pixel in PICT and leave a copy of the PICT 
GROB on the stack. If you do the same thing with #12655h (ABUFF) 
you will need to FREEZE the display so that you can see the result, 
otherwise as soon as the program finishes the stack will be redrawn and 
the stack GROB will be reset. The two SYSEVAL addresses return the 
actual stack and PICT GROBs, not copies of them, so that if you write to 
one of these GROBs the display will change.

The routine is not especially quick, but when the core of it is used in other 
machine code routines, the speed improvements are very impressive. For 
now, GRPIXON is only really useful if you have a GROB that you want 
to draw in, without using PICT.

Finally, it is worth saying that Im no expert on the intricacies of 48 
assembly. If you can find improvements that can be made to this piece of 
code, please let me know.

