Jump to content
  • entries
    53
  • comments
    536
  • views
    68,404

Sync'ing while thinking.


Guest

1,193 views

My first attempt at stabilizing the frame height while Four-Play is thinking did not go so well. From what I understand, a frame lasts for 19912 cycles (76 cycles/line * 262 lines). To set the timer to last a whole frame, you would write #18 to T0124T. Then the timer would count down to 0 for 19456 (1024*19) cycles. At this point the timer is set to 255, and counts down once per clock, so it would loop from 255 to 0 twice in the last 456 clocks of the frame.In order to write to VSYNC at the correct time, you'd want to read INTIM before it flips to 255. Ideally you'd read it at 0. Then you would wait 456 cycles to reach the end of the frame (minus the overhead to read INTIM, etc.) If you do not read INTIM soon enough, its value changes every cycle and might as well be random.Does my understanding of the timer sound correct? Here is the code I tried to do VSYNC in time. It comes at the the beginning of a routine that is called often while the game is thinking.

Check4InARow       lda INTIM       bne CheckVertical       jsr SyncCheckVertical...Sync      sta WSYNC      sta WSYNC      sta WSYNC      sta WSYNC      sta WSYNC      sta WSYNC      lda #2      sta VSYNC      sta WSYNC      sta WSYNC      sta WSYNC      lda #0      sta VSYNC      lda #18      sta T1024T       rts 

18 Comments


Recommended Comments

It's not strictly necessary to send VSYNC signals while the game is thinking, as long as the screen is blanked, or there is a barn door effect, background color change, etc. If you don't send VSYNC, nothing bad will happen, though the TV might roll slightly when returning to the normal kernel.

Link to comment
TV might roll slightly when returning to the normal kernel.

Thanks, Fred. That's what I'm trying to prevent. Unfortunately the roll is more than slight on my TV. It's quite egregious.
Link to comment

Rather than waiting for INTIM to count down completely, I usually arrange things so that the value of interest is slightly below 128. During my "think" process, I can use a "BMI" instruction to tell that I haven't reached the value of interest yet; once I'm into the kernel, I wait for INTIM to reach the proper value (I often use 125, but you could adjust this based upon how often you check INTIM).

 

The one limitation with this approach is that the kernel thinks only last about 127*64 cycles at a time (about 106 scan lines). But if you want to show ~48-line logo or message near the middle of the screen while the game is thinking, things should work out well.

Link to comment

They way I usually do it is set TIM64 at the end of the visible screen to 76*# lines to wait/64+1 then go of and do some before VSYNC work ending with a STA WSYNC / LDA INTIM / BNE loop. Then do the 3 VSYNC lines, set TIM64 again, then more work and the INTIM loop.

 

There are a couple of gotchas here. First is due to rouning / timing, it's possible to miss the 1->0 transistion during the INTIM loop. (I guess you could get rid of the WSYNC, but it makes Z26.log much smaller.) Certain values for # line will also be off by a line, so some tweaking may be required.

 

And if INTIM = 0 is missed, then the RIOT starts counting up by 1 each clock cycle, hitting 0 again 256 cycles later. That can produce some nasty frame glitches if your work takes too long.

Link to comment

Not syncing is no big deal on regular CRT TVs. You can still see color cycling or vertical playfield (like on the Supercharger load screen). The problem is with modern LCD and/or multi-resolution TVs and capture cards often drop to a black or blue screen if they don't detect a proper video signal. They aren't just passing the video signal directly on to the screen anymore. That can be pretty annoying especially if you intend to display something meaningful while not maintaining sync (as in the case of the Supercharger status bars).

 

One of the checkers games manages to think while maintaining sync. I don't know if it's the Atari one or the Activision one. You should check that out.

Link to comment
And if INTIM = 0 is missed, then the RIOT starts counting up by 1 each clock cycle, hitting 0 again 256 cycles later.  That can produce some nasty frame glitches if your work takes too long.

 

If the polling loop is

lp:
 lda INTIM
 bne lp

and INTIM is 249 the first time it's polled, the loop will run for 7*256 cycles if the branch doesn't cross a page. If the branch does cross a page, the loop will run forever unless INTIM is a multiple of eight when it's polled.

 

Using BMI/BPL, or else using CMP followed by BCS/BCC, is much safer.

Link to comment
And if INTIM = 0 is missed, then the RIOT starts counting up by 1 each clock cycle, hitting 0 again 256 cycles later.  That can produce some nasty frame glitches if your work takes too long.

I think there might be an alternative way to determine if the timer has expired. I'm not sure if anyone has done this before.

 

Anyway, the RIOT can be set to trigger an IRQ when time runs out. Of course, the IRQ pin is not connnected but you do have access to an IRQ status register, of which bit 7 is the timer status. The rub here is that reading INTIM will clear the bit, so you must poll the status register. You must also write to the timer differently to enable the IRQ (A3=1.)

 

So you would do this, I assume:

 

lda #??

sta TIM64T+8 ; store value plus enable IRQ

 

...

.1

lda INTIM+1 ; IRQ status

bpl .1

 

Maybe there are already equates for this, and maybe it's all been done before, or maybe I am simply wrong. (BTW, I thought I remembered that cosmic ark does something weird with the RIOT... maybe it uses this method?)

 

Anyway, I'm going off of this datasheet:

 

http://www.6502.org/documents/datasheets/m...s_6532_riot.pdf

 

EDIT: Looking again, Cosmic ark does appear to poll the IRQ status, but stores to TIM64T... I must have been wrong about the +8 :))

Link to comment
EDIT: Looking again, Cosmic ark does appear to poll the IRQ status, but stores to TIM64T... I must have been wrong about the +8 :))

 

If I recall, the +8 indicates whether the timeout should assert the /IRQ output pin. The flag gets set regardless. On the other hand, it's important to note that reading INTIM will clear the flag, so if you're expecting to see the flag you shouldn't read INTIM beforehand.

 

Frankly, I just like watching for the count to drop below 128. It's simple, it's easy, you can see how much below 128 it's fallen, and even gross overshoots won't cause bad behavior beyond the single unavoidable(*) screen flip.

 

(*) If a VSYNC is late, a screen flip may be inevitable; if the code is late coming out of vertical blank, a screen flip may be avoided by skipping a few scan lines. If it's hard to avoid occasionally coming out of VSYNC late, it may be worthwhile to have a "fallback" value of INTIM. In a Boulderdash-style game, for example, if INTIM has counted down too much to show the first line, the game could wait for the start of the the second line and resume the display there. The top line would flicker some when the CPU load gets too high, but the rest of the screen would stable.

Link to comment

Whoops, just reviewed the MOS datasheet. After N cycles of INTIM=0 (where N is 64 if TIM64T was set) INTIM starts at 255 and is decremented every cycle until it equals zero, where it stays until the next time TIM?T is set.

 

Hmm, reading INTIM+1 (the interrupt flags) is probably better than checking to see if INTIM=0 since there's no danger of missing that window. Just note that interrupt flag gets set on the 0->255 transition not the 0->1 transition, which may affect the value you load into TIM?T.

Link to comment

Thanks for the suggestions. I did take a look at Activision checkers, and it does maintain sync during the thinking state, but the frame size is not constant. The frames range from 260 to 263 lines. I figure if Activision couldn't stabilize the frame size, that's good enough for Four-Play too.

 

I changed the code to read INTIM+1, (thanks, Eric) and now the frame size ranges from about 258 to 266*. Unfortunately there is a side-effect that does not show in z26. On the TV, there is a horizontal black line in the middle of the screen that bobs up and down. If I didn't know better, I'd say that VSYNC is happening in the middle of the screen. Is this an inevitable effect of the frame size changing? Does anyone have any idea what's going on, and how to eliminate the line?

 

Latest source and binary are attached to the top of this thread. Here is the part of the code that handles sync:

 

Check4InARow
      lda INTIM+1; poll timer
      beq CheckVertical
      jsr Sync

; the polling is repeated in other places as well

CheckVertical
     lda gamestate
.
.
.

Sync
     lda #2
     sta VSYNC
     sta WSYNC
     sta WSYNC
     sta WSYNC
     lda #0
     sta VSYNC
     lda #19
     sta T1024T
     rts 

* Except in the transition between thinking and game states, but that shouldn't be hard to fix.

Link to comment
I changed the code to read INTIM+1, (thanks, Eric) and now the frame size ranges from about 258 to 266*.

That's probably too much variation for a stable display on many TVs.

Link to comment
Re: Syncing while thinking... I decided to implement some of your original ideas in Maze Craze 2.

 

I was originally going to just blank the screen, but I realized that the generation algorithm ran in a fairly tight loop, so I did T1024T=147, and check INTIM periodically and break out when INTIM=128.  Once in VSYNC, I set VBLANK, render a playfield then wait for INTIM=127, then send VSYNC, clear VBLANK and continue calculating.  That last 1024 cycles provides more than enough of a margin so I never miss.

 

Is the problem here that you can't check INTIM at least every few hundred cycles or so?

The problem is that the frame size is not constant. I think what's happening is that it is difficult to read INTIM on a regular basis. The minimax search is sometimes busy checking for four in a row, sometimes adding marbles, and sometimes removing them. It is difficult to predict what the program is doing at a given point.

Link to comment
Check4InARow
      lda INTIM+1; poll timer
      beq CheckVertical
      jsr Sync
.
.
.
Sync
     lda #2
     sta VSYNC
     sta WSYNC
     sta WSYNC
     sta WSYNC
     lda #0
     sta VSYNC
     lda #19
     sta T1024T
     rts 

 

This is not good, because if you check INTIM every 200 cycles you'll have 200 cycles worth of slop in when you see it. If you want stable sync, you really need to check when you're close to VSYNC time, and then wait for it to arrive precisely.

 

If thinking can be broken down into both large and small tasks, it may be helpful to do large tasks until you're close to the deadline and then start doing smaller ones. Also, if there are e.g. two tasks you want to do once per frame that will take two scan lines each, it may be helpful to have your kernel routine check how much time is left. If five scan lines or more, do both tasks, then wait until VSYNC time and VSYNC. If three or four, do one task, wait for VSYNC time and do the second task during VSYNC. If two lines or less remain, wait for VSYNC time, do one task, turn off VSYNC, and do the other.

 

This approach lets you accommodate a few scan lines of slop while minimizing CPU loading.

Link to comment
If you want stable sync, you really need to check when you're close to VSYNC time, and then wait for it to arrive precisely.

I think I understand now. This is what Fred just described, isn't it?

Link to comment
If you want stable sync, you really need to check when you're close to VSYNC time, and then wait for it to arrive precisely.

I think I understand now. This is what Fred just described, isn't it?

Cool. I just got a stable 272 lines per frame. Now that I understand what to do, it's simple. :cool:

Link to comment
If you want stable sync, you really need to check when you're close to VSYNC time, and then wait for it to arrive precisely.

I think I understand now. This is what Fred just described, isn't it?

Essentially. I set T1024T=147, then do this periodic check while calculating:

       LDA INTIM
       CMP #129
    BCS .skip
    jsr .dosync
.skip

 

Then in .dosync, I have something like this:

.dosync
 LDA #8
 STA VBLANK

; render playfield here

.1
 LDA INTIM
 BPL .1
 LDA #2
 STA VSYNC
 STA WSYNC
 STA WSYNC
 LDA #0
 STA WSYNC
 STA VSYNC
 STA VBLANK
 LDA #147
 STA T1024T
 RTS

 

Before I finished writing this, I noticed that INTIM needs to be checked more often in Maze Craze 2, as I sometimes miss INTIM=128. I think this will be an easy fix (I apparently have two tight loops and I just placed the check in one of them.)

 

Also, I am making 259 scanlines (but 3 more WSYNCs will fix this.)

Link to comment
I am making 259 scanlines (but 3 more WSYNCs will fix this.)

After getting a stable 272, adjusting to 262 was easy. What I did is similar but a little different from your code. I also ended up with 259 and padded it with 3 WSYNCs.

 

Check4InARow
      lda INTIM; poll timer
      bmi CheckVertical
      jsr Sync

Sync
     lda INTIM
     cmp #126
     bne Sync
    ; frame ends up at 259 lines here
     sta WSYNC
     sta WSYNC
     sta WSYNC
     lda #2
     sta VSYNC
     sta WSYNC
     sta WSYNC
     sta WSYNC
     lda #0
     sta VSYNC
     lda #146
     sta T1024T
     rts 

Link to comment

Not to tell folks what to do :cool: but a cool idea for a hack would be to take the games that don't sync while they think and hack them so they do. :)

Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...