Jump to content
  • entries
    40
  • comments
    39
  • views
    28,372

Drawing List brainstorming


brpocock

840 views

So, here's the latest brainstorming with regards to drawing lists for sprites.

 

Sprite positioning, due to tight kernel timing constraints, are based upon every scanline whacks of HMOVE. That means the HMPx settings are limiting the difference of horizontal positioning between subsequent sprites in one player graphic.

 

Put another way, the number of lines without graphics for GRPx is equal to the difference in horizontal position between two sprites sharing that player graphic.

 

I'm using a judgement code to evaluate each sprite for potential inclusion in the two lists for GRP0 and GRP1 each frame. In order to present the minimum flicker, each sprite in turn is given first chance to draw, more or less guaranteeing that for n sprites over n frames, each sprite will be fully drawn at least once within those n frames.

 

There's one exception: the user-player-sprite, i.e. the player-character, is always given first priority on the GRP0 player. Thus, the player-character will not flicker, ever.

 

The judgement process first walks the drawing lists looking for an open slot. An open slot is decided by having no current sprite drawing lines mapped to it. (Repositioning lines and sleep lines are the other two possibilities.) When an open slot is not found, we consider the lists to be solved and continue to drawing the frame.

 

When an open slot is identified, each sprite is judged by looking at its horizontal and vertical positioning. If its vertical positioning is within the opening, then a computation is made to determine the number of lines needed to reposition GRPx from the prior sprite, to the sprite under consideration, and then to the subsequent sprite. If too much horizontal repositioning is required, no sprite lines could be drawn, so its value is zero. If the sprite would have visible lines, then the number of visible lines is recorded as well as the sprite number, unless a previously considered sprite would have had more lines drawn. (i.e. the first sprite found with the most lines possible visible.)

 

Logically, that push breaks down to pseudocode like

if (best_lines_seen < this_lines_seen) {
  best_lines_seen = this_lines_seen;
 best_sprite = this_sprite;
}

 

The lists could potentially display as many as five sprites for each player graphic, if they are aligned vertically, but that situation is very unlikely.

 

The static-length sprite and drawing-list arrays are undecided slightly as I'm tweaking the RAM usage heavily, but I hope to handle eight on-screen sprites and therefore eight drawing-list entries gracefully. That's the number of hardware sprites on my alma mater Commodore VIC-II chip, and seems to be a comfortable number of moving objects at the very large scale of the 8x6 display grid.

 

Again, sprites are guaranteed first shot once for number_of_sprites frames, so with eight sprites including the player-character, each sprite would be guaranteed a full draw at least once per 7 frames, or (worst-case) appearing about 1/7th to 1/8th of every second (PAL, NTSC). Not great, but that's a worst-case scenario of all seven non-player-character sprites occupying the same vertical area as the player-character, which is thankfully unlikely.

 

All this drawing-list processing makes use of INTIM for vertical retrace handling and if I find it running away may get capped off periodically (every n frames use a simpler but less fair mechanism) if the script handler starts to suffer on CPU cycles. (Remembering that Ursa scripts are running under the vertical retrace as well to control these NPC sprites!)

8 Comments


Recommended Comments

That style of sprite relocation will avoid having to blow any scan lines on RESxx timing loops, but repositioning a sprite that way will take eleven scan lines. It may be useful to have a few variations of one of your sub-kernels, each of which has a RESxx in a different place. You could use one of those to get the positioning close to where it should be (say within 30 pixels), and then use HMOVEs to get it to its final place within five scan lines (rather than eleven).

 

Depending upon your exact register and RAM usage, the simplest way to handle this might be to use two bytes in RAM for each major line of display as a code pointer; after displaying each major line, perform an RTS to go to the routine for the next line. This will only cost six cycles, and eliminates the need for other looping constructs.

Link to comment
It may be useful to have a few variations of one of your sub-kernels, each of which has a RESxx in a different place.

 

I've played around with something like this, but it's been inefficient on the overall. It may work its way back in, but I don't have much time in kernel for branching and such, and the kernel is unrolled, so any variation would have to be replicated 8 or 24 times. Not to say something with RESPx won't make it in -- just that it seems unlikely.

 

Depending upon your exact register and RAM usage, the simplest way to handle this might be to use two bytes in RAM for each major line of display as a code pointer; after displaying each major line, perform an RTS to go to the routine for the next line. This will only cost six cycles, and eliminates the need for other looping constructs.

 

That would be nice, but there are 48 effective lines per screen, so that would be 96 bytes of RAM right there... of course, that's impossible.

Link to comment

Regarding the player sprite and no flicker, I have a slightly different oppion.

 

IMO flicker is less noticable when it is constant. E.g. a sprite constantly flickering at 30 Hz looks better than a sprite switching between flicker and no flicker.

 

That being said, maybe you a allow the player sprite to constantly flicker at 30 Hz and give the other 50% to the other sprites.

Link to comment
Regarding the player sprite and no flicker, I have a slightly different oppion.

 

IMO flicker is less noticable when it is constant. E.g. a sprite constantly flickering at 30 Hz looks better than a sprite switching between flicker and no flicker.

 

That being said, maybe you a allow the player sprite to constantly flicker at 30 Hz and give the other 50% to the other sprites.

 

This puzzled me until re-reading. You're refering to the non-player sprites, i.e. that they will have more visible flickering due to the fact that they may enter/leave "bad areas" vertically causing such flicker?

 

I see the point, there. I think I'm taking that to heart, in a way: I'll try to work in that the player-character only gets preferential treatment on odd frames, or something to that effect, giving it a guaranteed 50% display time with fair competition for the other 50%.

Link to comment
That style of sprite relocation will avoid having to blow any scan lines on RESxx timing loops, but repositioning a sprite that way will take eleven scan lines. It may be useful to have a few variations of one of your sub-kernels, each of which has a RESxx in a different place. You could use one of those to get the positioning close to where it should be (say within 30 pixels), and then use HMOVEs to get it to its final place within five scan lines (rather than eleven).

 

OK ... I did some math and I'm leaning towards your interpretation on RESPx hits.

 

Each HMPx hit could potentially yield effectively +/- 7 clocks movement. (Yes, 8 one way, 7 the other, the math is easier pretending orthogonality.) The effective sprite positioning range is based upon 9 columns into which the first pixel of a sprite might go ... a sprite could be left of the playfield proper, or hanging off the right edge, as long as one pixel is within the playfield boundaries, and I'm being cheap on the logic and not trying to mask the part falling into the blank PF0 areas, although the omnipresent HMOVE bars do that very thing on the left ... So 9 columns times 4 playfield pixels of 4 clocks each ... minus one playfield pixel at the far left, since the 8 pixels double clocked sprite would be completely outside of the playfield at that point ... so that's (8 * 4 * 4 + 3 * 4 ) = 140 clocks potential display range. Worst-case scenario is moving from a sprite at position 0 to a sprite at position 139, giving a requirement to whack HMOVE 20 times. That's 20 scanlines wasted, or about one row (6 rows of 24 scanlines each in the "24-line kernel." Yes, it's insane.)

 

Giving the option to whack RESPx at the 1/3 marks -- i.e. at positions 46 or 93 -- makes the maximum possible distance required about 46 clocks, which could be done in 7 lines, plus the one line for the RESPx.

 

This would alter the drawing list to resemble the C code:

 

struct drawing_list_entry {
 bits verb : 2; /* enum 0, 1, 2, 3 */
 union direct_object {

struct resp_hit {
   bits position : 1;
   bits unused : 5;
};
  struct hmp_hit {
 bits hmp_value : 4;
 bits repeat_count : 2;
  };
  struct draw {
 bits start_line : 3;
 bits num_lines : 3;
  };
  struct sleep {
 bits sleep_lines : 6;
  };

 };

 bits sprite_index : 3;
 bits wasted : 5;
}

 

In a perfect world I could only include the sprite_index value after drawing starts, only wasting 5 bits when in a draw phase. In my world, I probably can't afford that logic.

 

This upsets the tea table in a beneficial way. Thanks for the brainstorming assistance.

 

With any luck, I'll have a stable sprite drawing kernel later this week, as time permits. (80 h/w at first job and about 20h/w at second job get first priority.)

 

Incidentally, I have on paper (!!) the status screen display code rewritten to use missiles to show boss locations on the level map ... I have to key it all in to get the damned centre-of-screen timings worked out, since the "keys you are holding" and "map of the level" displays are in a split screen, but I think it will work. I think I'll have a "you are here" dot as well.

 

I'm cheating and just setting COLUPF to blank out the map if you don't have the map item for the current level.

 

Incidentally, all level-specific variables are wiped out by leaving the level (dungeon), so you will have to more or less play through the levels in order, making the overworld map not entirely useful, but . . . there's 128 bytes of RAM, damn it.

Link to comment

P.S. to my just-prior notes.

 

Hitting a RESPx line as I've just declared would require it to be on a venetian blinds line (skipped line), since timing is too tight when drawing the playfield to wedge them in at specific clock-points.

 

That means potentially costing two additional sleep lines after the prior sprite.

 

Also, hitting RESPx for both player graphics at once would be impossible (at the same 1/3 marker).

 

That just means that I'll need to detect this (relatively unlikely) circumstance and introduce one extra sleep line for one of the player-graphics, costing an extra couple of lines.

 

Oh -- and, of course, the first sprite in each player graphic is positioned using RESPx precisely, since I have a few lines before I start drawing the playfield to "waste."

Link to comment
I see the point, there. I think I'm taking that to heart, in a way: I'll try to work in that the player-character only gets preferential treatment on odd frames, or something to that effect, giving it a guaranteed 50% display time with fair competition for the other 50%.

Actually that's exactly the opposite what I meant to say. ;)

 

Let the player-character flicker with constant 30Hz and reserve the other 50% solely to the other objects.

Link to comment

D'oh! Better. So, logic something like:

 

If field is odd/even, inject player-character (sprite 0);

 

Regardless as to whether that has happened, proceed to process drawing-lists starting from ( inc first_pick_sprite / bcc .ncar / lda #1 / sta first_pick_sprite )

 

... That saves on figuring out where the player-character falls in rotation.

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