|
Post by D-Type on Jun 3, 2018 15:59:31 GMT -5
The Malban BIOS disassembly/comments for Wait_Recal says "it must be called every refresh cycle, or your vectors will get out of whack".
Looking at the code, it's waiting for timer t2 to timeout, then resetting the timer and running the calibration routines, before the rest of the user-created program does it's work.
The timer defaults to 1/50th of a second, so you've got 30,000 cycles to play with, before the timer times out and, if it does, you'll start to get screen flickering.
If your code is fast enough, then there are still some of the 30,000 cycles left, in which case, you're program will wait at the Wait_Recal until the 1/50 of a second is up before continuing.
So really, if your code can run in 1/50th of a second, the Wait_Recal routine only needs to be called once per main loop and that will help make your game run smoothly and not speed up or slow down depending on what processing is going on - it allows you to cut your processing into constantly sized "frames".
If you're doing a lot of graphics and they are starting to go "out of whack" during your 1/50th of a second, you wouldn't call Wait_Recal, you'd call F2E6 Recalibrate instead.
Is my understanding correct here?
Before such a thing as the VIDE debugger with it's "tracki" tool, how did you go about testing to see if your code ran in under 1/50th of a second? Would you have to flash a led or something in main loop?
|
|
|
Post by gauze on Jun 4, 2018 12:45:03 GMT -5
I've never used Recalibrate, I think based on use in the BIOS it's only used after changing Refresh rates inside a loop. Wait_Recal calls Recalibrate via Set_Refresh too.
You probably want Reset0Int and Reset0Ref instead inside frames.
Additional comments appreciated.
|
|
|
Post by D-Type on Jun 4, 2018 18:00:25 GMT -5
Thanks for your reply.
I can see that there's a number of parts to Wait_Recal and each of those parts have lower level parts. It's something I'm getting used to with assembly programming, that "sub-routines" often don't just have an RTS or a nice clean end of routine marker, the code just carries on into another named "sub-routine", which could be called from somewhere else. It's like a structured programmers nightmare, just like BASIC with only GOTOs!
In my adventures with Forth, everything has a terminator, so it's much easier to see what's going on. I'm also thinking to just reimplement some/all the bits of the BIOS I need using Forth because it saves the API calls I'm already having to make. Not initially fast, but best to get something going first and optimise it later.
The more I look at the BIOS, the more I'm thinking it's actually all quite simple, many of the routines are actually just a single base routine with slightly different calling methods. I can make it so I can just call the relevant Forth version of the code with parameters on the stack and, at least initially, do away with the DPR Direct addressing optimizations.
For example, instead of:
Wait_Recal: LDX Vec_Loop_Count ;Increment loop counter LEAX 1,X STX Vec_Loop_Count
I can just do: 1 Vec_Loop_Count +!
What is there not to like about that?
Well, apart from +! being defined as: CODE +! \ n/u a-addr -- add cell to memory D X TFR, 6 # ( D) PULS, X 0, ADDD, X 0, STD, 6 # ( D) PULS, NEXT ;C
:-)
|
|
|
Post by mikiex on Jun 8, 2018 7:04:14 GMT -5
I think you need to get into Assembly more, if you want to shorten it - then look into macros. I find it clean, it's just a case of immersing yourself in it.
|
|
|
Post by D-Type on Jun 8, 2018 15:35:38 GMT -5
I think you need to get into Assembly more, if you want to shorten it - then look into macros. I find it clean, it's just a case of immersing yourself in it. Well that wasn't the original idea...but I'm sure I'll have to at some point, because I don't want an inefficient and messy interface between Forth and the hardware! Anyway, Forth assemblers don't use macros, they use Forth...meaning I would have to modify the Forth compiler to inline, which would sort-of give the same end result as a macro. Assembly is certainly clean and probably zen-enlightening, exactly the same as Forth. But I'm digressing, none of this answers my original question regarding the usage of the timer, so I'm going to assume my understanding is correct and start experimenting
|
|
|
Post by gauze on Jun 8, 2018 17:20:30 GMT -5
I'm not sure what your question was, can you give a concise summary of what you wanted to know?
|
|
|
Post by D-Type on Jun 8, 2018 17:20:56 GMT -5
OK, let me answer my own Wait_Recal question...I wrote a Forth version of Malban's tutorial line1.asm, which (repeatedly) displays a line on the screen and zapped it down to the VecFever RAMdisk. I called the Forth word "DL", i.e. Draw Line. At the start of each refresh, Wait_Recal is called and my theory was that it waits until the rest of 20ms frame is completed (i.e. 50Hz timer times-out) then recal's everything and you redraw your vectors. At the start of the loop, I inserted a counter that displays to my PC terminal, via a VecFever serial port. At the end of the loop, I inserted a check to see if a key has been pressed and, if it was, the display loop terminates. Then, I started the DL loop and at the same time started a stopwatch. Then after 10 seconds, I exited the DL loop and stopped the stopwatch. The line was displayed on the Vectrex during test (I saw it with my own eyes!), the loop counter showed it ran ~500 times in 10 seconds. This proves my theory that Wait_Recal limits your code to 50Hz updates See it in the video below:
|
|
|
Post by gauze on Jun 8, 2018 22:35:55 GMT -5
yeah that is well known. unless you change the Vec_Rfresh variable from default (which is $C83D they should have used the symbolic name in the BIOS listing you are also looking at, but did not) here is the default value being set: LDD #$3075 ;Init refresh time to $7530 STD <$C83D 0x7530 = 30000 1500000/30000=50hz this should tell you all you need to know I think
|
|
|
Post by D-Type on Jun 9, 2018 2:49:14 GMT -5
... by those who know it! I had read the Malban tutorial and 30,000 and hex equivalent stuff before, but it still wasn't clear to me what Wait_Recal was actually doing. The clue for me came from the original RUM name of the code, FRAME20 (I think). And of course, there's no better way of learning than by doing. Forth interactive terminal prompt FTW!
|
|
|
Post by Malban on Jun 9, 2018 20:00:47 GMT -5
Hi,
you pretty much figured out what you need :-). Just another 2 smartass cents from me:
You should not skip the WaitRecal. 1) It (as you discovered) ensures a steady time measurement of a single "round" (if < 30000 cycles) 2) It also prepares time measurement for the next round (Resets T2) - this you should not skip
The "Timer" part of WaitRecal is (time wise) negligible in comparison to the Recalibration part. The calibration takes more than 800 cycles - since it basically moves the vectorbeam to the $7f7f and than to $8080 with a scale of $ff.
Also: Don't underestimate Recalibrate - in some cases I call Recalibrate to get a steady screen.
Some analog part of the vectrex "remembers" last positions - even after zeroing.
You can see that e.g. when you program a loop and draw a point with increasing X coordinate from $80 to $7f (and than $80 again - basically an x position "inc" loop") with a scale of $7f.
In each "round" after the above "movingdot" - do a zeroRef and than draw a dot at a "fixed" position.
Each time the "moving" dot leaves the screen on one side and reappears on the other the "fixed" dot will also do a "jump" (always depending on the quality of your personal vectrex of course).
One way to (nearly) fix that is to do a Recalibration between the two calls.
Malban
|
|
|
Post by D-Type on Jun 10, 2018 16:04:18 GMT -5
. .
The "Timer" part of WaitRecal is (time wise) negligible in comparison to the Recalibration part.
The calibration takes more than 800 cycles - since it basically moves the vectorbeam to the $7f7f and than to $8080 with a scale of $ff. . . Thanks for your thoughts, it's makes sense. What I'm doing now is experimenting with scale, intensity and direction/positioning, I haven't quite got the hang of it yet, but I'm getting there. I recently read your Good Deal Games interview and it said, for VFrogger, you were using a fixed scale factor of 6, which had the consequence of requiring you to use lots of small lines to draw long lines. As I've been experimenting with the BIOS line draw, I've found that to draw a straight line from bottom left to top right (or between any 2 corners), you have to use the maximum FF scale factor, else the line doesn't go all the way. A scale factor of 7F only gets half way. Given that interview was a long time ago, has your view changed on the way scale factor is used (when using BIOS drawing functions, i.e. custom routines are less expensive in cycles)? It would seem to be (before I really understand it) that you'd use the smallest scale factor you can get away with to get the length you need. Here's today's code (excuse the inefficiency), it draws a border on the screen...there's ~1 pixel gap between start and end on the Vectrex display: : DL \ -- ; 0 BEGIN 1 + DUP U. \ Send a frame counter to display on the terminal
_Wait_Recal \ _Intensity_7F \
FF VIA_t1_cnt_lo C! \ set scaling factor
-3F -3F _Moveto_d \ move beam to bottom left
0 Vec_Misc_Count C! \ must be set to 0 to draw one vector 0 7F _Draw_Line_d \ draw the line
0 Vec_Misc_Count C! \ must be set to 0 to draw one vector 7F 0 _Draw_Line_d \ draw the line
0 Vec_Misc_Count C! \ must be set to 0 to draw one vector 0 80 _Draw_Line_d \ draw the line
0 Vec_Misc_Count C! \ must be set to 0 to draw one vector 80 0 _Draw_Line_d \ draw the line
KEY? \ User key press exits loop UNTIL DROP
;
|
|
|
Post by Malban on Jun 11, 2018 7:21:16 GMT -5
Oh... I could write pages over pages about my thoughts about drawing vectors. Since it is always a good idea to refresh the screen in 50Hz to avoid flicker wobble... ... you usually want to achieve "fast" vector drawing - especially if you need to get many vectors on the screen. The trade off for "fast" (in vectrex relation) is always "memory usage". For games using many vectors and 50Hz screen refresh - you need fast drawing routines, where every cycle saved is precious. - if you want to use the BIOS routines, you can/must reduce the vector count to achieve 50Hz - or - you don't reduce the vector count and simply display with only 30Hz (or so) - and the screen probably also starts to "wobble" The scale has DIRECT influence on the time a vector takes to be drawn. Optimally you should draw every vector with a strength of +127 or -128 and only adjust the scale. The single drawn vector will than be fully optimized. Thing is - you have to change the scale every vector (if the vectors come in different lengths) - which costs time, that the overall cost for a list of differently sized vectors increases again. In VFrogger I chose scale 6 as my personal minmal scale and drew every object with it. (VectorPatrol e.g. is often drawn with a scale of 9). BUT --- Using (really) small scales can also have negative effects: If you use a very small scale and a very large strength, vectorlists are not really "closed" anymore. E.g. scale 3 strength 120. If you draw a figure using these values you will see on a real vectrex "gaps" between vectors. This is based on the fact, that you technically can not switch the light on/off at the same time as you switch on/off movement. The effect can always be seen using BIOS routines, but the "gap" is based on the relation strength/scale - the higher that relation is, the larger (in relation to the vectorlength) the gap is. To some degree you can circumvent that if you program your own custom routines - but using BIOS you can not. My personal order of vector draw routines (decreasing speed): - custom draw every line of your figure, vector coordinates are loaded "immediate" -> huge memory inpact known usage: player shots in vectorblade - smartlists -> first officially used in VectorPatrol (but to some extend/same technique in VRelease and Thomas Sontowski games) - specialized "fixed scale" routines using loop unrolling (VFrogger...) - manually modified BIOS routines/macros - BIOS routines * of these Draw_Vlp is the fastest Also noteworthy if you want to draw a list consisting of MANY vectors are "synced" draw routines. These can circumvent some "drift" - which will always occur but depends on the quality of your personal vectrex.
Malban
|
|
|
Post by D-Type on Jun 11, 2018 17:24:18 GMT -5
Many thanks for the extended insight there, you're confirming the basics for me, but the real gold nuggets are the things like very low scaling factor losing positional accuracy, so I guess it's some trial and error to come up with parameters that are fast and look good. Is there any merit in using scaling factors that are a multiple of each other, e.g. $4, $8, $10, $20 etc? I remember reading your blog post about the Vector Pilot "Smartlists" and not really understanding what it was all about, now it might be worth a 2nd read (3rd...4th...) I have a long way to go before implementing all the optimisations you mention, but that's OK, if it was doable in five minutes, it wouldn't be much fun! It also gives me an appreciation of how much effort all the people have put into making anything genuinely playable on Vectrex. Also, I'm going to have to dig into the Vector lists that VIDE can produce...I need some reasons to use VIDE some more, the experiments I'm doing at the moment are run from the terminal prompt via VecFever, no PC emulator/debugger used. Here are the Forth "primitives" I'm using with the VecFever serial port, in case they were ever going to be emulated... 7FFB EQU v4eTxStatReg \ Read, negative if transmit buffer is in use, positive otherwise 7FFB EQU v4eTxByteReg \ Write 7FFC EQU v4eRxStatReg \ Read, zero if no data received, otherwise != 0 7FFD EQU v4eRxByteReg \ Read, upon reading, 7FFC is automatically cleared 7FFE EQU v4eLEDReg \ LED Register, probably read/write?
CODE KEY? \ -- f return true if char waiting 6 # ( D) PSHS, CLRA, v4eRxStatReg LDB, NE IF, -1 # LDB, THEN, NEXT ;C
CODE KEY \ -- c get char from serial port 6 # ( D) PSHS, BEGIN, v4eRxStatReg LDB, NE UNTIL, v4eRxByteReg LDB, CLRA, NEXT ;C
CODE EMIT \ c -- output character to serial port BEGIN, v4eTxStatReg LDA, MI UNTIL, v4eTxByteReg STB, 6 # ( D) PULS, NEXT ;C
|
|