It is currently Sun Jun 17, 2018 3:23 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 14 posts ] 
Author Message
PostPosted: Thu Jun 14, 2018 5:17 am 
Offline
User avatar

Joined: Mon Jan 23, 2017 8:08 pm
Posts: 14
Location: Boston, MA
I'm working on a game and had been using sprites for certain objects, but because they don't move very often and because I can have far more than 8 per line, I wanted to store them in the background while they're not active. I wrote the code to do so, which does write the correct sprite to the correct background tile, however, every time I do, the screen will briefly scroll for what seems to be a frame before it will reset back to where it should be a ~frame later (haven't actually confirmed its exactly one frame, but I think scroll gets reset every frame with the code I'm working off of). I tried resetting the scroll in the same cycle manually, but it didn't fix the issue. It seems to mostly scroll vertically, but occasionally it's been diagonally.

I'm assuming there's something quirky about writing to the PPU_ADDRESS that I'm missing OR I'm writing to it at the wrong time? Its being written during NMI. I'm using nesdoug's tutorials/example code as a baseline, so part of it is extracted here.

Code snippet below (for all the code and this exact snippet, its here):

Code:
void remove_from_background(int x, int y) {
   index = y/8;
   //Nametable 0
   PPU_ADDRESS = 0x20 + (index/8);
   PPU_ADDRESS = 32*(index % 4) + (x/8);
   PPU_DATA = 0x01;
   Reset_Scroll();
}


Any help or info would be greatly appreciated :) Thanks in advance!


Top
 Profile  
 
PostPosted: Thu Jun 14, 2018 5:38 am 
Offline
Formerly WheelInventor

Joined: Thu Apr 14, 2016 2:55 am
Posts: 1606
Location: Gothenburg, Sweden
I’m at work and can’t read the code atm, but it sounds like you need to do your updates prior to setting the scroll value, preferrably as part of the nmi routine. You can set some variable during your game logic to let the nmi routine know that this update is pending.

edit: citing the nesdev wiki from under ”common pitfalls”

”PPUSCROLL must always be set after using PPUADDR ($2006). They have a shared internal register and using PPUADDR will overwrite the scroll position.”

https://wiki.nesdev.com/w/index.php/PPU_scrolling

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
PostPosted: Thu Jun 14, 2018 5:46 am 
Offline
User avatar

Joined: Mon Jan 23, 2017 8:08 pm
Posts: 14
Location: Boston, MA
I vaguely remembered there was a detail like that, so that's why I added the "Reset_Scroll" call. Here is the full code of that method:

Code:
void Reset_Scroll (void) {
   PPU_ADDRESS = 0;
   PPU_ADDRESS = 0;
   SCROLL = 0;
   SCROLL = 0;
}


Top
 Profile  
 
PostPosted: Thu Jun 14, 2018 6:23 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2089
Location: DIGDUG
I really should change my tutorial. This is a bit 'cargo cult' programming. Changing the PPU Address isn't necessary to set the scroll. Properly, the nametable selection should be a write to 2000 and then only 2 writes to 2005 (scroll) are required.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Thu Jun 14, 2018 6:59 am 
Offline
User avatar

Joined: Mon Jan 23, 2017 8:08 pm
Posts: 14
Location: Boston, MA
TIL the term "cargo cult" :) To be fair, even when doing a project from assembly from scratch, this was a part I definitely saw many issues with and were very difficult to debug.

I'm still confused though. My intention is not to scroll the screen. My intention is to write to one specific background tile, but I'm seeing undesired scrolling afterwards. Even if I just replace "Reset_Scroll" with the two calls to set the X/Y scroll ("SCROLL = 0"), I still see the undesired scrolling. Not sure what 2000 has to do with it.

Also, thanks for all the quick replies. These forums are amazing.


Top
 Profile  
 
PostPosted: Thu Jun 14, 2018 7:26 am 
Offline
User avatar

Joined: Sat Jan 09, 2016 9:21 pm
Posts: 399
Location: Central Illinois, USA
I'm no expert, but It sounds like one of a couple things might be happening:

1. You're not actually writing the bg tiles during vblank, even though you think you are.
2. Somewhere else you're writing to the address register ($2006) without a final write to the scroll register.


I'd pull up a debugger, set a breakpoint on your code in question, and see if it's running during vblank like you expect. (Mesen has a PPU Status panel that indicates whether you're currently in vblanking or not) Step through and see if you're writing to the address register ($2006) outside of vblank, and make sure that any writes to the address register are followed up by setting the scroll before vblank ends.

_________________
My games: http://www.bitethechili.com


Top
 Profile  
 
PostPosted: Thu Jun 14, 2018 7:56 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2089
Location: DIGDUG
Quote:
Not sure what 2000 has to do with it.


From the wiki on Register 2000, the bits are

VPHB SINN

NMI enable (V), PPU master/slave (P), sprite height (H), background tile select (B), sprite tile select (S), increment mode (I), nametable select (NN)

Writing to the 2 lowest bits changes which nametable is rendered.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Thu Jun 14, 2018 8:22 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2089
Location: DIGDUG
By the way. Writes to the PPU must happen during v-blank or with rendering off (2001 register). And, Y scroll can't (normally) be set during rendering, and should also be done during v-blank or with rendering off.

(there's a ton of exceptions and complicated tricks, but for simple use, this is generally good advice).

Misaligned scroll can result from poorly timed writes.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Thu Jun 14, 2018 8:57 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10510
Location: Rio de Janeiro - Brazil
The PPU has an address register that it uses to access VRAM, be it when the programmer is reading/writing from/to VRAM or when the PPU itself is reading it for the purpose of rendering a picture to the TV. This means that these things are always connected and can't happen simultaneously, which is why VRAM can only be changed during vblank, a time when the PPU is NOT accessing VRAM.

Setting the scroll is essentially pointing the address register to the NT location that's supposed to appear at the top left corner of the screen (using the more convenient interfere that's register $2005), and if you touch the address register after doing that, you mess up that setting and the scroll will not be what you expect. If you're still getting unintentional scrolling after resetting the scroll in your vblank handler, you might be changing the VRAM address outside of vblank, which doesn't work the same way and should only be done in case of raster effects.

Debug your program in an emulator like Mesen or FCEUX to make sure that all PPU accesses are indeed taking place during vblank, and that the scroll is being reset properly afterward (1 write to $2000 to select the name table, 2 writes to $2005 to set the scroll within that name table), still during vblank time.


Top
 Profile  
 
PostPosted: Thu Jun 14, 2018 9:27 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6340
Location: Canada
I would suggest a few things, starting with a few rules of thumb: never manipulate registers directly in C, and never write timing-sensitive code in C.


So, this recommendation is to do all register interactions in assembly. Don't assign to them directly in C. Yes it technically works sometimes, but not always, and there's a lot of ways it can fail.

Wrap those interactions up in an interface to an assembly library, something like set_ppu_address(0x2110) instead of two assignments to the $2006 register, ppu_write(0x50) or ppu_write_block(buffer, 32) instead of direct $2007 manipulation, etc.

Finally, have an interface for setting the scroll like set_scroll(258,0), but don't have that function immediately write to $2005; keep track of the desired scroll, and use those variables later in your (assembly) code that turns on rendering ($2001) immediately beforehand. There's no need to write the scroll registers at any time earlier than this point, and the further you move it away from that, the more chances there are to accidentally stick something conflicting in between.


Turning on rendering needs to be done with precise timing, specifically within vblank. If you do it anywhere else, you'll get one frame of garbage before it resynchronizes itself. This should be timed using the NMI handler. If you make your library handle the scroll registers at that time, there's no possibility that it well be mistimed or corrupted by accidental intervening code.

From the C side, I'd create a render_on() interface that waits for the next vblank, at which point it sets up the scroll registers and turns rendering on, before returning control back to your C program. This would also be paired with a render_off() that waits for vblank to disable rendering (so you don't get a half-blanked frame) before you start using the set_ppu_address/ppu_write interface suggested above. Even when writing directly in assembly, while I would definitely just write STA $2007 for efficiency, I would still put my timed rendering code in common subroutines like render_on/render_off and call those, to keep the number of places in my code that have to do critically timed accesses to a minimum.


Top
 Profile  
 
PostPosted: Thu Jun 14, 2018 10:26 am 
Offline
User avatar

Joined: Mon Jan 23, 2017 8:08 pm
Posts: 14
Location: Boston, MA
I REALLY like this idea to remove register interactions from the C code. Even without the potential cc65 undesired affects in translating to assembly, I do find it much more confusing having hardware specific code/values in C. And even better, as Doug pointed out, I'm suffering from Cargo Cult programming and this would be a great way to take his example and build my own solution from both sides that I can own and understand fully. I'll give it a try when I have some free time and see if it fixes my issue.

Thanks again to everyone for their thoughts/advice!


Top
 Profile  
 
PostPosted: Thu Jun 14, 2018 10:37 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2089
Location: DIGDUG
Quote:
I'm suffering from Cargo Cult programming


We both are. Obviously I saw this bit of code somewhere, and copied it without considering it first.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Thu Jun 14, 2018 3:34 pm 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2089
Location: DIGDUG
Quote:
interface to an assembly library


You can make a macro that has inline assembly.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Fri Jun 15, 2018 5:05 am 
Offline
User avatar

Joined: Mon Jan 23, 2017 8:08 pm
Posts: 14
Location: Boston, MA
So I didn't have time to fix it the best way yet, but confirmed that some of your suggestions do fix the issue. To prove it, I stole a page out of doug's example code and just kept 2 vars for the memory location of the background tile in the assembly code that I can set from the C code and the assembly will write to the background every frame. So I'll obviously want to make sure this is only when I need it, but it at least proved that you folks were right. Thanks again!


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 14 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group