Jump to content
  • entries
    657
  • comments
    2,699
  • views
    899,712

Sprite driver rewritten


SpiceWare

2,715 views

Sprite driver has been rewritten to work with the new kernel. I've also written an alternative sprite driver that improves performance, though there is a minor tradeoff when using it. I've set OPTIMIZE to 1, which causes some extra 6507 code to come into play. The extra code extends the display by 10 scanlines in order to show what's left in TIM64T for Vertical Blank and Over Scan. Some TVs may not like displaying a 272 scanline image, so it's possible this ROM will roll for some people. My C=1084S displays it just fine, so I find it useful for testing how code changes will affect performance.

blogentry-3056-0-35739100-1391374139_thumb.png

  • RESET = "start game"
  • SELECT = return to "menu" (rainbow screen)
  • Left Joystick - move humanoid. Move to edge of room to trigger room change.
  • Right Joystick - move Otto
  • TV Type Color = Original sprite flicker routines, B&W = alternative sprite flicker routines
  • Left Difficulty B = Berzerk Rooms, A = Frenzy Rooms
  • Right Difficulty B = Normal robot distribution, A = Maximum robot distribution, lined up horizontally for extra flicker.

The VB/OS display will change color based on which sprite routine is active. Green = original flicker logic, Grey = alternative flicker logic.

 

Left & Right difficulty setting changes only take affect when changing rooms.

 

Example of original flicker routine with C (12) time remaining in Vertical Blank.

blogentry-3056-0-30445200-1391378228_thumb.jpg

 

Alternative flicker routine with D (13) time remaining in Vertical Blank

blogentry-3056-0-29140800-1391378236_thumb.jpg

 

Flicker Logic

Ideally when playing the game we'd like to see non-flickering sprites such this:

blogentry-3056-0-36923100-1391370057.png

 

However, the Atari 2600 only has 2 players to draw the sprites with. In order to show more than 2 objects in a row we could use NUSIZx to set duplicate and triplicate mode (like in Space Invaders), but doing so will cause the missiles to also show up in duplicate or triplicate mode. Space Invaders doesn't use the missiles (all shots are drawn with the ball object), but we don't have that option so we'll use flicker. Traditional Atari games will dedicate one of the players to the human player and flicker the other player for all other in game objects. This is due to limited processing time on the 2600. With the extra horsepower of the ARM chip, the routines in Frantic will use both players to flicker all objects. Surprisingly, Amidar uses this method as well.

 

Psuedocode for the flicker logic is this (if you'd like to see the actual code, look for function DisplaySprites in file main.c which is located in the directory custom😞

loop AGE from OLDEST_AGE to 0, decrement AGE by 1 each time thru loop
    loop i from 0 to MAX_SPRITES
        if sprite[i] is active and spriteage[i] = AGE
            can we draw with player0?
                yes - draw it, set spriteage[i] to 0
                no - can we draw with player 1?
                    yes - draw it, set spriteage[i] to 0
                    no - increase spriteage[i]
        endif
    endloop i
endloop AGE
 

Spriteage is how many frames since that specific sprite was last displayed. If there's no flicker then spriteage will always be 0.

 

Basically the above logic tries to draw sprites with the oldest age first. It then tries to draw the next oldest sprites, and so on until we've processed all ages.

 

Frantic supports 24 sprites, numbered 0-23. Due to the way the original routine was written, the lower numbered sprites ended up with a higher priority in the flicker logic. Because of this priority, I used sprite 0 for the humanoid and sprite 1 for Evil Otto. Flicker of the humanoid and 2 robots using the original routines would look like this:

blogentry-3056-0-14873700-1391370067.gif

 

The problem with the above flicker logic is the more flicker there is, the longer it takes to process because of the outer loop. If the max age is 0 we only go thru the loop one time, but if the max age is 3 then we go thru the loop 4 times. The idea I had Thursday night was to eliminate the AGE loop by changing the code to work like this (actual code in function DisplaySpritesNew in main.c😞

loop i from 0 to MAX_SPRITES
    set SHOWN to FALSE  x = FLICKERLIST[i]
    if sprite[x] is active
        can we draw with player0?
            yes - draw it, set SHOWN to TRUE
            no - can we draw with player 1?
                yes - draw it, set SHOWN to TRUE
    endif
    if SHOWN
    	add X to end of FLICKERLIST2
    else
        add X to start of FLICKERLIST2
endloop i
copy FLICKERLIST2 to FLICKERLIST
 

Now, instead of keeping track of ages, we keep a list that's in the order to attempt to draw the sprites. If a sprite isn't drawn it goes to the front of the list, if it is drawn it goes to the end. Example list of 5 sprites:

0, 1, 2, 3, 4

 

We attempt to draw 0, since we can it goes to the end of the new list:

_, _, _, _, 0

 

We next attempt to draw 1, since we can it goes to the end of the new list:

 

_, _, _, 1, 0

 

We attempt to draw 2, we can't so it goes to the front of the new list:

2, _, _, 1, 0

 

We can draw 3 and 4, so the new list (for next screen update) is this:

2, 4, 3, 1, 0

 

The new routine works well and is more efficient than the old routine as seen in the photos at the top of this blog entry. However, the priority for lower numbered sprites no longer works so instead of the above example where the humanoid didn't flicker, I'd see this:

blogentry-3056-0-95765600-1391370072.gif

 

or this:

blogentry-3056-0-74442000-1391370078.gif

 

 

What I ended up doing was instead of adding sprites to the start or end of a single list, I added sprites to the start of two lists that were then merged together. The psuecode for the lists is:

loop i from 0 to MAX_SPRITES
    ...
    if SHOWN
        add X to SHOWN_LIST
    else
        add X to NOT_SHOWN_LIST
endloop i
FLICKERLIST = NOT_SHOWN_LIST + SHOWN_LIST
 

 

Example list of 5 sprites:

0, 1, 2, 3, 4

 

We attempt to draw 0, since we can it goes in the shown list:

SL = 0, _, _, _, _ NSL = _, _, _, _, _

 

We next attempt to draw 1, since we can it gets added to the shown list:

SL = 0, 1, _, _, _ NSL = _, _, _, _, _

 

We attempt to draw 2, we can't so it goes in the not-shown-list:

SL = 0, 1, _, _, _ NSL = 2, _, _, _, _

We can draw 3 and 4, resulting in:

SL = 0, 1, 3, 4, _ NSL = 2, _, _, _, _

 

The merge step combines those lists into the new flicker list:

FL = 2, 0, 1, 3, 4

 

With this revision, the flicker ends up like this:

blogentry-3056-0-68207600-1391370084.gif

 

 

It's not as good (for the humanoid) as the original routine, but much better than the initial rewrite and we still have the better performance. For ease of comparision, here's all the flicker examples lined up:

blogentry-3056-0-14873700-1391370067.gif Original

blogentry-3056-0-95765600-1391370072.gif initial rewrite

blogentry-3056-0-74442000-1391370078.gif alt initial rewrite

blogentry-3056-0-68207600-1391370084.gif final rewrite

 

ROM

frantic_20140202.bin

 

Source

Frantic20140202.zip

10 Comments


Recommended Comments

Moved the ROM and Source to end of blog entry as the line spacing was such that people might have overlooked the Flicker Logic section of the entry.

Link to comment

IIRC Manuel (CyberGoth) discovered a single pass through a bubble-sort routine had some good anti-flicker properties for StarFire. (This was back in the StellaList days...)

Link to comment

It took a couple of tries, but I've figured out how to re-add the priority for the lowered numbered sprites:

list SHOWN_LIST, NOTSHOWN_LIST, FLICKERLIST = {0..MAX_SPRITES}
 
loop i from 0 to MAX_SPRITES
  SHOWN_LIST = {}
  NOTSHOWN_LIST = {}
  x = FLICKERLIST[i]
  if sprite[x] is active
    can we draw with player0?
        yes - draw it & add X to SHOWN_LIST
        no - can we draw with player 1?
          yes - draw it & add X to SHOWN_LIST
          no - add X to NOT_SHOWN_LIST
  else
    add X to NOT_SHOWN_LIST
  endif
  FLICKERLIST = NOT_SHOWN_LIST + SHOWN_LIST
endloop i
loop i from MAX_SPRITES downto 2
  if FLICKERLIST[i] < FLICKERLIST[i-1]
    swap( FLICKERLIST[i], FLICKERLIST[i-1] )
  endif
endloop i

three sprites on line:
FL = 0,1,2 -> SL = 0,1 NSL = 2 -> FL = 2,0,1 -> 0,2,1
FL = 0,2,1 -> SL = 0,2 NSL = 1 -> FL = 1,0,2 -> 0,1,2

four sprites on line:
FL = 0,1,2,3 -> SL = 0,1 NSL = 2,3 -> FL = 2,3,0,1 -> 0,2,3,1
FL = 0,2,3,1 -> SL = 0,2 NSL = 3,1 -> FL = 3,1,0,2 -> 0,3,1,2
FL = 0,3,1,2 -> SL = 0,3 NSL = 1,2 -> FL = 1,2,0,3 -> 0,1,2,3
Link to comment

Cool!

 

Hmm, if I'm reading the four sprites on line correctly, it looks like sprite 0 doesn't flicker while the other 3 flicker at 20 Hz.

 

In the original if three are in a line then sprite 0's priority would prevent it from flickering, but if 4 are in a line then everything flickered at 30 Hz.

 

In the current routines the flicker is evenly distributed. If 3 are in a line then the flicker is 40Hz (each sprite is on for 2 frames, off for 1), if 4 are in a line then flicker is 30Hz, etc.

 

I'll take a closer look when I next work on Frantic. My folks are running errands in the city today (they live in Lake Jackson, about 50 miles south of Houston) so I don't foresee any work on Frantic getting done this evening.

Link to comment

My folks wrapped up their day earlier and made it home before I got off work, so I went ahead and tried this out.

 

    for (i= MAX_SPRITES-1; i>1;i--)
    {
        if (gFlickerList1[i] < gFlickerList1[i-1])
        {
            shown = gFlickerList1[i];
            gFlickerList1[i] = gFlickerList1[i-1];
            gFlickerList1[i-1] = shown;
        }
    }
It made it so sprite 0 never flickered, which made overall flicker worse. Think I'll stick with what I have.

 

Thanks for taking the time to look.

Link to comment

Yeah, I was thinking about it later and prioritization is great for the highest priority sprite, but much worse for the rest of the sprites. Although it would be great to have the player/Otto flicker less than the rest of the sprites, I'm not sure it's easily do-able in a general case.

Link to comment

Yeah, the Age method worked great for the player/Otto, but the better run-time for the new method is a major plus.

Might be worth setting up the code to compile for either method, using #IFDEFs to control it, and see if the Age method could be re-enabled at a later date. I suspect it'll depend upon other performance improvements as screen jitter was a problem in the past.

Link to comment

After playing a few rounds, the lack of priority boost is actually a benefit - I spent the majority of the timing looking at the robots, not the humanoid. With the old routines if the humanoid lines up with 2 robots then the robots are drawn 30 out of every 60 frames. With the new routines they're drawn 40 out of 60.

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...