Tachyon 5 - Old 'straight line' algorithm - for vector graphics, multi-stepper control, sound synths
I first saw this in a computer magazine back in around 1979. There was always rivalry over who could draw 'straight lines' of pixels the fastest. There were a lot of algorithms more complicated than they needed to be! The algorithm not only applies to vector graphics; so for example where two or more steppers need to move from a start position, ending simultaneously at the target position. In the Tachyon code that follows, the number of axes is set with constant AXES. It's currently a 3 axis engine. The start and end positions are set for the demonstration in tables MYSTART and MYGOAL. Here's the code:-
--- Straight line algorithm for Tachyon 5v7 --- Useful for anywhere where a bunch of numbers have to change in fixed non-integer ratios to arrive simultaneously at a target --- e.g. vector graphics, multi-stepper control, sound synthesis, lighting faders PUBLIC 3 := AXES --- number of axes to run AXES 4* := AXESL --- byte loop count for the arrays of longs AXES longs POSN --- Start and present position array AXES longs GOAL --- the target array AXES longs ACC --- axis accumulator PRIVATE AXES longs DELTA --- set to GOAL minus initial POSN long TBASE --- timebase, set from the biggest DELTA long SLCOUNT --- counter to keep track of progress pri [SL] ( -- ) --- slarray indexer I 4* + ; --- inc or dec the I'th member of POSN, dependent on whether --- I'th member of DELTA is pos or neg pri STEPPOSN ( -- ) POSN [SL] DELTA [SL] @ 0< IF -- ELSE ++ THEN ; pri !DELTAS ( -- ) --- initialise DELTAS AXES FOR I 4* >R GOAL R@ + @ POSN R@ + @ - DELTA R> + ! NEXT ; --- TBASE set to the largest DELTA pri !TBASE ( -- ) 0 AXES FOR DELTA [SL] @ ABS MAX NEXT TBASE ! ; --- initialise straight line engine for new line pub !SLINIT ( starts goals -- ) ACC AXESL 0 FILL --- zero the accumulators GOAL AXESL CMOVE --- set the target position POSN AXESL CMOVE --- set the starting position 0 SLCOUNT ! !DELTAS --- calculate all goals minus starts !TBASE --- TBASE set to the largest DELTA ; --- step one place along the 'straight line', POSN is set to new position --- flg returned true if arrived at GOAL, else false pub SLSTEP ( -- flg ) AXES FOR ACC [SL] DUP @ DELTA [SL] @ ABS + ( ptr-to-ACC ACC+|DELTA| -- ) DUP ( ptr-to-ACC ACC+|DELTA| ACC+|DELTA| -- ) TBASE @ > ( ptr-to-ACC ACC+|DELTA| -- ) IF TBASE @ - ( ptr-to-ACC ACC+|DELTA|-TBASE -- ) STEPPOSN --- dec POSN if DELTA -ve, else inc POSN THEN SWAP ! ( -- ) --- and save new ACC value NEXT SLCOUNT @ TBASE @ = SLCOUNT ++ ; --- Test words TABLE MYSTART -15 , 10 , 13 , --- test start values TABLE MYGOAL 15 , 21 , -7 , --- test goal values --- test of !SLINIT pub test!SLINIT ( -- ) MYSTART MYGOAL !SLINIT ; --- print AXES size array of longs pub .ARR ( adr -- ) AXES FOR DUP [SL] @ . SPACE NEXT DROP ; --- Print all variables pub .SL ( -- ) CR ." POSN = " POSN .ARR CR ." GOAL = " GOAL .ARR CR ." DELTA = " DELTA .ARR CR ." TBASE = " TBASE @. CR ." SLCOUNT = " SLCOUNT @. CR ; --- demo that straight line alg. works pub SLDEMO ( -- ) MYSTART MYGOAL !SLINIT --- initialise straight line engine ." STRAIGHT LINE DEMO" CR .SL CR ." POSN steps by coordinates ..." CR BEGIN SLSTEP POSN .ARR CR UNTIL ." End of straight line" CR ; --- time the demo, so no printing in the loop pub SLTIME ( -- ) MYSTART MYGOAL !SLINIT --- initialise straight line engine ." STRAIGHT LINE TIMING DEMO" CR .SL CR ." POSN steps by coordinates ..." CR LAP BEGIN SLSTEP UNTIL LAP TBASE @ . ." Steps in " AXES . ." Axes took" CR .LAP CR ." End of straight line" CR ;Run SLDEMO to see the coordinates at each step. Run SLTIME to get some idea of how long the engine takes. So - I bet there's clever ways of making this faster - or has anyone got an even faster algorithm?. Might be good as part of a ROM? :-)
Comments
TF5> SLTIME --- STRAIGHT LINE TIMING DEMO
POSN = -15 10 13
GOAL = 15 21 -7
DELTA = 30 11 -20
TBASE = 30
SLCOUNT = 0
POSN steps by coordinates ...
30 Steps in 3 Axes took
67,920 cycles = 849.000us
End of straight line
ok
Seems pretty impressive to me.
Now a little off topic - I couldn't copy-paste larger portions of the program let alone do it in one go. It somehow got messed up and I had to do it in small chunks to make it work. There must be some more clever method to upload a bulkier code all at once.
The question is how to do it efficiently ?
The way I did it is simply a no go for more than a couple of lines.
Might it be my terminal setting for the line delay is too small ?
I use tera term under windows with 0 ms inter-character delay and 15 ms end of line delay and that seems to load any size of file reliably. I agree, it sounds like a terminal delay issue.
I think the code as it stands is fast enough for stepper motor control, but would need converting to assembly language for drawing many straight lines on a video screen, probably.
I use ascii-xfr with a line delay of around 3ms while leaving the minicom terminal connected. It's very fast that way.
Doug ( our resident cpu designer) had dreamt up a z80 emulator from two 4-bit TI slice ALUs and a shed load of bipolar roms to do the microcoding. We collected the bits together, but it never made the prototype stage. It would have run at the heady heights of 20MHz, instead of the 2MHz that the first z80s ran at. Doug would have liked the propeller chips, he'd have had all eight cogs whizzing hard! I remember his hastily scribbled pages of cordic code for rotating stuff, so echoes there of the p2.
Tachyon 5v7 iirc was upgraded for faster serial downloads so you can experiment with the line delay but 3 to 5 should be ok and 15 is way over the top. But this assumes you are loading in block mode using TACHYON <code> END which strips away the usual echo and prints line numbers and messages with a final report. USB serial suffers a lot when you are echoing everything back along with prompts etc and then the line delay is no longer distinct on the serial end and many lines may be appear on the serial rx line without any delay. I should do a proper capture of that too one day.
BTW, the code optimization is taking into account that it is not efficient nor recommended to use the return stack for juggling and that R@ is provided as a convenience but it is horribly inefficient. On the other hand a simple I 4* is very fast, so either call [SL] or better still, just code it in directly as I 4* +
Remember what happens if you push data onto the return stack and forget to pop it? CRASH!
It's good to bat these small bits of code around each other and benefit from others speed tricks - + it might get people reading tachyon, with the aid of the glossary, even if it's only to translate the algorithm to another language! It's best to start off written in a simple way so others can easily understand the algorithm + you've got something that at least works. Then start speeding it up progressively, which can make it harder to read.
If there was another array of longs, each member set either 1or -1 ready to add to POSN and the DELTA array made all positive values, both these things done at initialisation, then SLSTEP wouldn't need any decisions about direction, nor the ABS word every step
This is cool stuff. By chance do you remember the magazine / issue(s) where this competition was published? I love the "archeology" of computers and would really like to track this stuff down.
-joe
No worries. I remember reading in I think "PC Tech Journal" articles by Michael Abrash about all kinds of wizardly ways to speed up EGA/VGA access. Also, back in the TRS-80 days, there was a magazine that challenged how to write the "fastest" game-of-life engine for that machine.
Fun times!
-j
btw, Isn't >R IF -1 ELSE 1 THEN R> better coded with SWAPs?
Keep the code coming. It helps me to see how I can or where I need to improve Tachyon itself. I keep thinking that SLSTEP could be optimized somehow, maybe not, but maybe. I noticed that there isn't much difference if you define I 4* + as a routine. But if it were coded as PASM it wouldn't ned to push and pop the stack and would only 320ns (@96MHz).
That is a huge improvement in speed. Lateral thinking works.
It's a pity today's education is mainly based on tests, tests, tests. You just memorize the answers and the brain correlates them with the questions.
I do not envy the next generations if this trend continues. It wasn't like that when I was a schoolboy many decades ago...
My memory for stuff I wasn't all that interested in has never been good. For that and other reasons I didn't do that well in exams. Ever since I left uni, though, I've enjoyed learning and using stuff I was interested in. Now I'm retired, that's even more so with so much knowledge available on-line. I used to buy Practical Wireless and other magazines - no more, there is far more on the internet.
e.g. I installed Punyforth a few days ago on a little wifi processor module, the ESP-12 from ebay. Can be programmed to be an IoT device by itself or act as a serial-to-wifi bridge for P1 or P2. Yet another platform to explore and build into projects. Don't think I'm going to start automating the home though! Enough breaks down without making it more complex ;-)
Used to read those in my youth years in the 60’s. IIRC one issue came with a little plastic pouch with 3 miniature screwdrivers - 2 flat (yellow and green) and 1 Philips (red). Still have and use them