Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
A Fast(er) Plot Routine for Boriel Basic
#1
I orginally posted this on SpectrumComputing Forum - copied here in case anyone wants to play with it :-)

Hi

Hope this helps someone else :-)

I saw a post by Andy Dansby who writes at https://zxspectrumcoding.wordpress.com/

He has created a fast plot routine, (hellaplot), so I thought I'd try and convert it to a Boriel Basic (BB) subroutine, just for fun and learning - and as it was a single routine.

I had to do a little bit of reading, but also had to ask for help from the BB telegram community, as to begin with the code was crashing the emulator. Turns out it was a simple fix when you know how - FASTCALL was needed when passing parameters for ASM to use from a SUB routine.

Andy's code needs the x/y locations in DE, so I had to do a little bit to get them set up like this - I assume there might be some more efficiency if different registers were used as BB passes from the subroutine call, the first parameter into A, and the second parameter is the second item on the stack (the first being the return address).

When I started, xPos was the first parameter, and yPos the second (as I like x,y), but this meant to get them into DE, took another load (POP DE, LD E with D, LD D with A, compared to POP DE, LD E with A)

So some "non exact" timings - plot included as the library in BB takes about 15 seconds to fill up the screen with "points". Andy's one takes 5 seconds to do the same.

As far as Andy's code goes, I understand the comments, but not exactly how it works :-)

Anyway, code below :

SUB FASTCALL HellaPrint(yPos as UBYTE, xPos as UBYTE)
' HellaPrint prints an x,y pixel
' 0,0 is top left
' Original code by Andy Lansby
' https://zxspectrumcoding.wordpress.com/
' yPos is first, as it save a LD instruction when putting xPos and yPos into DE
ASM

JP START ; Put this in as it's like this on the Wiki ASM desciption - can it be avoided?
X_PositionBits: defb 128,64,32,16,8,4,2,1
; Might there be a quicker way to do the above, so it's not needed every time the program is run? or does it not work like this and is created once at compile time?

START: ; plot d = x-axis, e = y-axis
; A contains the yPos, xPos is on stack.
POP HL ; Pops the return address into HL
POP DE ; xPos is on the stack, and it needs to be in D
LD E, A ; A has our first paramter (yPos), load it into E. D should be Xpos, E should be yPos
PUSH HL ; Puts the return address back onto stack ...
; 166 T states per pixel
XOR A ; reset A to 0 and flags to default
LD A,E ; load Y plot point
RRA ; rotate Right --- divide in half
SCF ; turn on Carry flag-
RRA ; rotate right with the carry flag
OR A ; set flag S on - C flag off
RRA ; rotate Right --- divide in half

LD L,A ; temp store in L
XOR E ; XOR the Y value
AND %11111000 ; mask out bottom 3 bits
XOR E ; XOR the Y value

LD H,A ; store High byte
LD A,D ; load X plot point
XOR L ; XOR the temp value
AND %00000111 ; mask out unwanted bits
XOR D ; XOR the X value
RRCA ; divide by 2
RRCA ; divide by 4
RRCA ; divide by 8

LD L,A ; store Low byte
; now we have the full address
; now use LUT to find which bit to set
LD A,D ; load X plot point
AND %00000111 ; mask out unwanted bits

; use a LUT to quickly find the bit position for the X position
LD DE,X_PositionBits ; load LUT address into DE
ADD A,E ; Add A to E to get offset into table
LD E,A ; E now points to the LUT entry
LD A,(DE) ; load answer into A

; output to screen
OR (HL) ; or with contents of address HL
LD (HL),A ; load address HL with Answer from A
END ASM
END SUB
Reply
#2
Excellent. How much faster is this?
Boriel Basic uses the ROM to get the pixel address on the screen (and save memory), but the rest is done in the code.

Also, to make this routine fully compatible, you should work with HL = 0x0000 (as is the screen starts at 0x0000, not at 0x4000) and do
LD DE, (SCREEN_PTR)
ADD HL, DE
before writting to it.
This will allow the screen buffering to work with your routine too.

NOTE: Other drawing routines (DRAW, CIRCLE...) use the Bresenham algorithm already, and each pixel address is computed relatively to the previous plotted pixel.
---
Boriel
Reply
#3
(2025-10-30, 11:12 AM)boriel Wrote: Excellent. How much faster is this?
Boriel Basic uses the ROM to get the pixel address on the screen (and save memory), but the rest is done in the code.

Also, to make this routine fully compatible, you should work with HL = 0x0000 (as is the screen starts at 0x0000, not at 0x4000) and do
LD DE, (SCREEN_PTR)
ADD HL, DE
before writting to it.
This will allow the screen buffering to work with your routine too.

NOTE: Other drawing routines (DRAW, CIRCLE...) use the Bresenham algorithm already, and each pixel address is computed relatively to the previous plotted pixel.

I didn't do any "millisecond" timings, but to fill the whole screen with the Boriel point, took about 15 seconds, substituting this one took about 5 - there are probably other/better ways to do it, and I don't know if there are other parts I've done incorrectly - eg it uses a very small lookup table - does it create that each time the procedure is called, or does it create it at compile time.

I only did this for "fun" and learning, so it might be useful for someone to adapt and use.  I understand each "command" but not how they all interact together to do what they do :-)
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)