Diagonal scrolling help

Discussion of hardware and software development for Super NES and Super Famicom.

Moderator: Moderators

Forum rules
  • For making cartridges of your Super NES games, see Reproduction.
User avatar
NovaSquirrel
Posts: 372
Joined: Fri Feb 27, 2009 2:35 pm
Location: Fort Wayne, Indiana
Contact:

Diagonal scrolling help

Post by NovaSquirrel » Sun Apr 21, 2019 5:41 pm

I've been developing for SNES and I want to have levels that are two screens tall, except to save video RAM for tiles instead, I'm using 64x32 tilemaps, but I'm running into problems with some occasional wrong tiles on the top half of the level, corresponding to the right tiles for the bottom half in the same position. I think aside from that it works.

Image

Since this is my first time doing diagonal scrolling it's hard to tell if my implementation is wrong, or if the logic I'm trying to implement in the first place is wrong (so any implementation of that logic would be flawed) so I figured getting more eyes on my code would help.

I have a ColumnUpdateBuffer that gets DMA'd to ColumnUpdateAddress vertically, a full 32 tiles tall. I also have a RowUpdateBuffer that similarly gets DMA'd to RowUpdateAddress horizontally and it's always 64 tiles wide for simplicity, and I only write to the portion of it that will be visible. My level buffer is in LevelBuf and it is 256 wide * 32 tall * 2 bytes per metatile, arranged in a series of columns from left to right.

When the scrolling has changed: (simplified)

If the X scroll value passes a tile boundary:
  • If scrolling left, select a column 2 tiles left from the new scroll position
  • If scrolling right, select a column 34 tiles right from the new scroll position
  • (Selected column number will be called SelectedColumn)
  • Set ColumnUpdateAddress to tilemap base address + SelectedColumn AND 31, use second screen if needed
  • Prepare a pointer to start reading from level data (Level column is SelectedColumn / 2)
  • -----
  • Y = Y scroll position counted in metatiles
  • X = Y, restricted to fit ColumnUpdateBuffer
  • ColumnUpdateBuffer[X++] = Tile numbers[Level pointer[Y++]]
  • Repeat the previous step 15 more times, wrapping X around in the buffer.
If the Y scroll value passes a tile boundary:
  • If scrolling up, select a row 1 tiles up from the new scroll position
  • If scrolling down, select a row 29 tiles down from the new scroll position
  • (Selected row number will be called SelectedRow)
  • Set RowUpdateAddress to tilemap base address + SelectedRow * 32
  • Prepare a pointer to start reading from level data (Level column is X scroll position in metatiles, minus 1)
  • -----
  • Y = SelectedRow / 2
  • X = X scroll position in metatiles * 2, minus 1, constrained to the buffer size
  • RowUpdateBuffer[X++] = Tile numbers[Level pointer[Y]]
  • Move Level pointer ahead one column
  • Repeat previous two steps 20 times (probably more than needed. I was trying to make it slightly wider than the screen, hence starting a bit to the left of it)
Or probably a lot more clearly, the code itself:
scrolling.s
renderlevel.s
Attachments
nova-the-squirrel-2.zip
(14.11 KiB) Downloaded 147 times

tepples
Posts: 21707
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Diagonal scrolling help

Post by tepples » Sun Apr 21, 2019 5:52 pm

"Y = Y scroll position counted in metatiles"
This needs to be based on the previous frame's Y scroll position, in case you cross a tile boundary in both directions at once.

User avatar
NovaSquirrel
Posts: 372
Joined: Fri Feb 27, 2009 2:35 pm
Location: Fort Wayne, Indiana
Contact:

Re: Diagonal scrolling help

Post by NovaSquirrel » Sun Apr 21, 2019 6:11 pm

Yeah I thought about problems from both the row and column updating at once. Changing that doesn't seem to fix it, and it's hard to tell exactly what's failing. I probably need to start over on the scrolling code but I don't really have correct guidelines to work from.

User avatar
tokumaru
Posts: 11438
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Diagonal scrolling help

Post by tokumaru » Sun Apr 21, 2019 6:31 pm

Garbage tiles in diagonal scrolling often happen because of mismatched vertical and horizontal tile map updates in the same frame.

If you generate the new tile data for movement in one axis immediately after moving in that axis but before moving in the other axis, consider that that data will be off by one if the movent in the other axis also happens to trigger an update. If the updates are carried out in the same order they were prepared, things are probably gonna be fine, if not, you may end up with old data overwriting new data.

I don't know in which order you're doing everything, so what you need to do is simulate or debug the case when both axis need updating, and see if there are any wholes in your logic that could be causing old data to overwrite new data or leave certain spots with no updates at all.

User avatar
NovaSquirrel
Posts: 372
Joined: Fri Feb 27, 2009 2:35 pm
Location: Fort Wayne, Indiana
Contact:

Re: Diagonal scrolling help

Post by NovaSquirrel » Sun Apr 21, 2019 6:49 pm

I actually move both axes at once, and then after that, check for and handle any updating. You think it's better to move an axis, prepare an update, then move the other axis and prepare that update, then do the two DMAs in the same axis order used here?

psycopathicteen
Posts: 2901
Joined: Wed May 19, 2010 6:12 pm

Re: Diagonal scrolling help

Post by psycopathicteen » Sun Apr 21, 2019 6:54 pm

Are you allowing 2 tiles on both sides so you can havr hdma effects?

User avatar
NovaSquirrel
Posts: 372
Joined: Fri Feb 27, 2009 2:35 pm
Location: Fort Wayne, Indiana
Contact:

Re: Diagonal scrolling help

Post by NovaSquirrel » Sun Apr 21, 2019 7:13 pm

psycopathicteen wrote:Are you allowing 2 tiles on both sides so you can havr hdma effects?
I haven't thought that far ahead. I have 2 because my previous game extended far further than 2 due to handling scrolling updates in 32 pixel wide chunks, and I was just being a little safe even while shortening it.

Looks like simply making X scroll changes happen and be handled before Y scroll changes doesn't help. I think I might actually be messing up the part that fetches from the level (or sets up the pointer) since it's fetching from the bottom rather than just being some other kind of garbage. Something tricky here is I don't know if it's the row updater or the column updater that's flawed.

User avatar
tokumaru
Posts: 11438
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Diagonal scrolling help

Post by tokumaru » Sun Apr 21, 2019 10:02 pm

NovaSquirrel wrote:You think it's better to move an axis, prepare an update, then move the other axis and prepare that update, then do the two DMAs in the same axis order used here?
I'm not sure, it's been a while since I coded an 8-way scrolling engine and I don't have any source code with me right now that I can check... but I distinctly remember having garbage tiles near the corners of the screen when scrolling diagonally and that having to do with the exact order in which horizontal and vertical updates were handled in the same frame.

User avatar
koitsu
Posts: 4214
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Diagonal scrolling help

Post by koitsu » Sun Apr 21, 2019 11:05 pm

I truly wish I could help here, but I'm having an extremely difficult time understanding any of what's being discussed. I clearly see "the problem" demonstrated in the screenshot, but I honestly do not understand how you'd end up with said problem unless your tilemap update data was incorrect based on several conditions (i.e. your code). Everything looks fine except for those few tiles; still screenshots don't help, sadly, but I'm not sure an animated GIF would either.

I would suggest stepping through your code in the bsnes-plus or Mesen-S debugger, and see what ends up in PPU RAM right after your DMA completes (since both emulators will let you examine system RAM in addition to PPU RAM). I assume you aren't running out of NMI time, either.

I've done diagonal scrolling before -- you just increment/decrement a 16-bit variable and write it to $210D-2114 as needed -- but this was simply panning backgrounds with a 32x32 layout (i.e. single-screen). I haven't done it with 64x32 or 32x64 or 64x64. But 64x32 may require you -- on vertical scrolls -- to update a row which shouldn't be visible (otherwise the player would see the update) -- most 64x32 games I see use a status bar of some kind to cover this, I believe. That said: if you have something that's two screens tall, why are you using 64x32 rather than 32x64?

As you've learned, the SNES is not like the NES at all -- there really isn't a "you need to do something with the A registers before the B registers" for screen panning. All of that NES-level nonsense is for the most part irrelevant.

User avatar
NovaSquirrel
Posts: 372
Joined: Fri Feb 27, 2009 2:35 pm
Location: Fort Wayne, Indiana
Contact:

Re: Diagonal scrolling help

Post by NovaSquirrel » Mon Apr 22, 2019 5:13 am

Yeah I get the impression this is something I'll have to figure out for myself and that it's probably a problem with my implementation somewhere.

I used 64x32 instead of 32x64 to avoid needing to black out the leftmost 8 pixels, though if it's good enough for Kirby Super Star then perhaps it's good enough for me, at least to have something 100% working for now.

Longer term for actually fixing this I feel like it'd be a good idea to go and thoroughly test the row/column updating code and see if I can give it fixed values that result in a problem every frame, and then from there both know exactly what kinds of values are breaking and have it in a state where I can step in and debug. Or, if necessary, just try and rewrite all the scrolling code fresh, being careful and testing a lot along the way. It's not necessarily a lot of work wasted.

psycopathicteen
Posts: 2901
Joined: Wed May 19, 2010 6:12 pm

Re: Diagonal scrolling help

Post by psycopathicteen » Mon Apr 22, 2019 7:48 am

Maybe you're not updating the corner tiles.

User avatar
tokumaru
Posts: 11438
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Diagonal scrolling help

Post by tokumaru » Mon Apr 22, 2019 2:10 pm

psycopathicteen wrote:Maybe you're not updating the corner tiles.
He did say that he's updating the whole 32 or 64 tiles, so I don't think he's missing any corners. It's true that sometimes people forget that they need to update areas wider/taller than the screen, because of the fine scroll.

User avatar
koitsu
Posts: 4214
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Diagonal scrolling help

Post by koitsu » Mon Apr 22, 2019 3:05 pm

And in the case of the SNES, there is no "fine scroll".

nocash
Posts: 1060
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: Diagonal scrolling help

Post by nocash » Mon Apr 22, 2019 3:24 pm

The screenshot looks as if only half of the 16bit map entry was written okay.
Ie. wrong tile number, but (seemingly) correct palette... or completely unchanged palette if it's same as old color?

For general testing: Maybe it helps if you remove the horizontal or vertical updating (to see which one is buggy).
And perhaps initially fill the whole screen area with known tiles/colors (to see what does or doesn't get updated later on).
Don't forget debuggers with vram viewers - that might also help so see what happens after each DMA.
koitsu wrote:And in the case of the SNES, there is no "fine scroll".
I think "fine scroll" was referring to scroll amounts less than 8 pixels (the SNES does fortunately support that) (and it can result in needing 33 tiles per line, instead of 32 tiles in unscrolled pictures).

psycopathicteen
Posts: 2901
Joined: Wed May 19, 2010 6:12 pm

Re: Diagonal scrolling help

Post by psycopathicteen » Mon Apr 22, 2019 6:47 pm

I looked through the source code and played with the demo and I think there is a missing corner tile. I think lowering the top loading seam would do the trick.

Post Reply