It is currently Sat Jul 20, 2019 2:18 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 24 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Thu Jan 03, 2019 8:46 am 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 97
Location: Gothenburg, Sweden
Hey! Sorry if this is a redundant post. I've found some other posts covering this topic but I was not able to dissect an answer to my problem.

So I'm trying my hand at sprite-0 hit detection but I instantly run into a problem. It seems my loop for detecting the hit only branches on the correct scanline (32, though the OAM Y value is set to 28) the very first frame I test for it. After that it always branches when on scanline 246.

What am I missing? I'm testing this on a "clean" slate so to say. That is I'm not doing this in my game project, as it would make solving the issue even harder.

Here's the source for my NMI routine. I've attached my test rom as well.

Setting a break point at C116 should show you where the branch happens.

Thanks for reading this!

Code:
NMI:
   
   pha
   txa
   pha
   tya
   pha
   
   ldx #0
   stx PPUMASK      ; disable rendering
   
   lda #<(OAM_buffer)   ; load OAM buffer into PPU
   sta PPUOAMADDR
   lda #>(OAM_buffer)
   sta OAMDMA
   
   lda #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
   sta PPUCTRL
   lda #%00011110   ; enable sprites, enable background, no clipping on left side, emphasis on blue
   sta PPUMASK
   
   @wait_sprite0:
      lda PPUSTATUS
      and #%01000000
      beq @wait_sprite0
      
      ; TODO: We've hit sprite 0. now do something.
      ; for some reason, this only seems to hit the right scanline the very first time.
      ; after that, it's always at around 246. what am I doing wrong?
   
   inc frame_cnt   ; inc frame_cnt to get out of waitNMI loop.
   
   pla
   tay
   pla
   tax
   pla
   
   rti


Attachments:
sprite-0_test.nes [24.02 KiB]
Downloaded 78 times
Top
 Profile  
 
PostPosted: Thu Jan 03, 2019 9:01 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11376
Location: Rio de Janeiro - Brazil
A hit only happens when an opaque sprite pixel overlaps an opaque background pixel. That might explain the scanlines between the top of the sprite and the actual hit. I haven't looked at your ROM because I'm on my phone, so I don't know what the graphics look like.

As for the premature hit at scanline 246, that happens because once set, the flag will remain set until the end of vblank (scanline 262 or so), when the PPU automatically clears it. If you start testing too soon, you'll detect the old hit before the PPU has a chance to clear the flag. The solution for that is to first wait for the flag to be cleared, and then wait for it to get set.


Top
 Profile  
 
PostPosted: Thu Jan 03, 2019 9:14 am 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 97
Location: Gothenburg, Sweden
tokumaru wrote:
A hit only happens when an opaque sprite pixel overlaps an opaque background pixel. That might explain the scanlines between the top of the sprite and the actual hit. I haven't looked at your ROM because I'm on my phone, so I don't know what the graphics look like.

I've made sure the sprite is always positioned on opaque pixels, so that part should be OK?
tokumaru wrote:
As for the premature hit at scanline 246, that happens because once set, the flag will remain set until the end of vblank (scanline 262 or so), when the PPU automatically clears it. If you start testing too soon, you'll detect the old hit before the PPU has a chance to clear the flag. The solution for that is to first wait for the flag to be cleared, and then wait for it to get set.

OK.. hmm.. Am i correct to assume the start of vblank triggers the NMI and that they run in parallel? Would I then solve the issue by just doing something that takes up a certain amount of cycles for the vblank to end? Or is there any way I can test and wait for the end of vblank before I test for my sprite-0 hit?


Top
 Profile  
 
PostPosted: Thu Jan 03, 2019 9:15 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11376
Location: Rio de Janeiro - Brazil
Also, since the sprite hit flag is located in bit 6, you can take advantage of the BIT instruction to test it, which results in a tighter wait loop. A faster wait loop helps reduce jitter, because it will detect hits sooner.

Here's what the wait itself could look like:

Code:
@wait_no_sprite0:
  bit PPUSTATUS ;copy bit 6 to V flag
  bvs @wait_no_sprite0 ;test again if set
@wait_sprite0:
  bit PPUSTATUS ;copy bit 6 to V flag
  bvc @wait_sprite0 ;test again if clear


Top
 Profile  
 
PostPosted: Thu Jan 03, 2019 9:26 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11376
Location: Rio de Janeiro - Brazil
pwnskar wrote:
I've made sure the sprite is always positioned on opaque pixels, so that part should be OK?

But you said the OAM Y value is 28, and the hit only happens at scanline 32. The PPU has a one scanline delay when rendering sprites, so the sooner a hit would happen in this case is scanline 29. Are your graphics designed for the hit to happen at the top of the sprite or does the opaque overlap only happens further down?

Quote:
OK.. hmm.. Am i correct to assume the start of vblank triggers the NMI and that they run in parallel?

Yes. The purpose of the NMI is to let you know that vblank started, so you can make use of that time.

Quote:
Would I then solve the issue by just doing something that takes up a certain amount of cycles for the vblank to end?

Yes, but that takes more code and is harder to manage (if you modify your vblank handler you may have to adjust this wait period). It's much simpler and safer to wait for the flag itself to be cleared.


Top
 Profile  
 
PostPosted: Thu Jan 03, 2019 9:30 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21507
Location: NE Indiana, USA (NTSC)
And to ensure your game doesn't completely crash if some oversight causes the background in this area not to be opaque, you can set a timeout by including the vblank bit in your test.
Code:
@wait_no_sprite0:
  bit PPUSTATUS ;copy bit 6 to V flag
  bvs @wait_no_sprite0 ;test again if V set

  lda #%11000000 ;test for at least one of bit 7 or 6
@wait_sprite0:
  bit PPUSTATUS ;copy bit 7 to N flag, bit 6 to V flag, and NOT(A AND status) to Z flag
  beq @wait_sprite0 ;test again if Z set

  bmi @sprite_0_was_missed

This means a sprite 0 miss is more likely to cause a momentary graphical glitch instead of a crash.

On the other hand, when debugging, you may prefer a hang to a momentary glitch to encourage you to investigate the root cause of the sprite 0 miss and thereby reduce momentary glitches in the release build. You can switch between hard failure for debug builds and recovery behavior for release builds by using conditional assembly, a feature of the major 6502 assemblers to determine what to assemble based on the value of a constant defined earlier in the program. I don't see what assembler you're using, but in ca65, it'd look something like this:

Code:
HANG_ON_S0_MISS = 1

@wait_no_sprite0:
  bit PPUSTATUS ;copy bit 6 to V flag
  bvs @wait_no_sprite0 ;test again if V set

  .if HANG_ON_S0_MISS
    lda #%01000000 ;test only bit 6
  .else
    lda #%11000000 ;test for at least one of bit 7 or 6
  .endif
@wait_sprite0:
  bit PPUSTATUS ;copy bit 7 to N flag, bit 6 to V flag, and NOT(A AND status) to Z flag
  beq @wait_sprite0 ;test again if Z set

  bmi @sprite_0_was_missed

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Thu Jan 03, 2019 12:44 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 8486
Location: Seattle
Quickly looking in Mesen ...

The problem is not that your sprite 0 handler isn't running. The bit is being set, &c.

The problem is that you aren't waiting for vertical blanking to end before you start polling the bit in the busy-wait in NMI, and the bit isn't cleared until vertical blanking ends.


Top
 Profile  
 
PostPosted: Thu Jan 03, 2019 4:19 pm 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 97
Location: Gothenburg, Sweden
Thanks for the help, everyone! I now have the sprite-0 detection branching consistently on the same scanline. Now I have another problem and that is that the scroll gets messed up after I follow my sprite-0 with some palette updates. I try to correct it by writing twice to $2005 and once to $2000 but I can't seem to get it to work. Here's my updated NMI and a rom.

Code:
NMI:
   
   pha
   txa
   pha
   tya
   pha
   
   ldx #0
   stx PPUMASK      ; disable rendering
   
   bit PPUSTATUS
   lda #$3f
   sta PPUADDR
   lda #$00
   sta PPUADDR
   
   lda #$0f      ; undoing the writes I do after sprite-0 here.
   sta PPUDATA
   lda #$00
   sta PPUDATA
   lda #$10
   sta PPUDATA
   lda #$20
   sta PPUDATA
   
   lda #<(OAM_buffer)   ; load OAM buffer into PPU
   sta PPUOAMADDR
   lda #>(OAM_buffer)
   sta OAMDMA
   
   ldx #0
   stx PPUSCROLL
   stx PPUSCROLL
   
   lda #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
   sta PPUCTRL
   lda #%00011110   ; enable sprites, enable background, no clipping on left side, emphasis on blue
   sta PPUMASK
   
   @wait_no_sprite0:
      bit PPUSTATUS ;copy bit 6 to V flag
      bvs @wait_no_sprite0 ;test again if set
   @wait_sprite0:
      bit PPUSTATUS ;copy bit 6 to V flag
      bvc @wait_sprite0 ;test again if clear
      
      ; TODO: We've hit sprite 0. now do something.
      
      ldx #0
      stx PPUMASK
      bit PPUSTATUS
      lda #$3f
      sta PPUADDR
      lda #$00
      sta PPUADDR
      
      lda #$0a      ; writing to the palette
      sta PPUDATA
      lda #$1a
      sta PPUDATA
      lda #$2a
      sta PPUDATA
      lda #$3a
      sta PPUDATA
      
      ;bit PPUSTATUS
      ldx #0
      stx PPUSCROLL
      stx PPUSCROLL
      
      lda #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
      sta PPUCTRL
      lda #%00011110   ; enable sprites, enable background, no clipping on left side, emphasis on blue
      sta PPUMASK
      
   inc frame_cnt   ; inc frame_cnt to get out of waitNMI loop.
   
   pla
   tay
   pla
   tax
   pla
   
   rti


EDIT: I forgot to mention that I'm incrementing the y value of the sprite-0 every frame now, incase the rom seems confusing.


Attachments:
sprite-0_test_02.nes [24.02 KiB]
Downloaded 76 times
Top
 Profile  
 
PostPosted: Thu Jan 03, 2019 4:31 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21507
Location: NE Indiana, USA (NTSC)
Whenever you update the palettes or anything else that uses $2006/$2007, the NES PPU forgets what the scroll position was. This means you have to rewrite the scroll position in $2005 (twice) and $2000.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Thu Jan 03, 2019 4:40 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 8486
Location: Seattle
pwnskar wrote:
;bit PPUSTATUS
ldx #0
stx PPUSCROLL
stx PPUSCROLL
PPUSCROLL only sets the Y coordinate at the very top of the screen. Afterwards you have to use PPUADDR (or both together) to set Y scrolling.


Top
 Profile  
 
PostPosted: Thu Jan 03, 2019 5:31 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11376
Location: Rio de Janeiro - Brazil
Setting the scroll mid-screen works differently from setting it during vblank. To have full control of the scroll mid-screen, you need to mix writes to $2005 and $2006. This is usually considered complicated by people who don't understand how scrolling really works on the NES. Here's the relevant information: https://wiki.nesdev.com/w/index.php/PPU ... r_controls

Things get significantly simpler if you don't need to set the fine Y scroll (i.e. the Y scroll is a multiple of 8). In this case you might get away with just writing the NT address of the tile where you want the scroll to resume to register $2006.

Since you're messing with mid-screen palette changes, let me warn you this is a notoriously hard thing to get right on the NES for a few reasons:

1- VRAM can only be accessed if rendering is turned off, and turning rendering of mid-screen can corrupt OAM on the *next* frame depending on whether there are sprites in the scanline where rendering is turned off;

2- When rendering is off and the VRAM address is pointing to the palette region, the color at that address is displayed on the screen, meaning that if you want to update colors without causing jittery rainbows to show up on the screen, you can really only do it during hblank, 1 or 2 colors per scanline;

3- Once rendering is turned off, the PPU's rendering logic gets a bit messed up, and sprites for example may need an entire scanline to fully recover, meaning that you may need a few scanlines of "nothing" during which you can do the palette updates without disturbing the visible picture;

4- After you're done messing with VRAM, you need to restore the scroll, which can be a little complex if you have a free-moving camera.

These are the main things you have to watch for when changing the palette mid-frame. Not surprisingly, not many games do it.


Top
 Profile  
 
PostPosted: Thu Jan 03, 2019 6:26 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 8486
Location: Seattle
tokumaru wrote:
2- When rendering is off and the VRAM address is pointing to the palette region, the color at that address is displayed on the screen, meaning that if you want to update colors without causing jittery rainbows to show up on the screen, you can really only do it during hblank, 1 or 2 colors per scanline;
Just in case this didn't sound bad enough, it's actually a little worse:
* 1 color, with a noticeable hole on the left of the previous scanline or right of the following scanline, if you want no empty scanlines at all. (Example: Taito's Indiana Jones)
* 3 colors per scanline completely disabled, displaying some random fixed color during the blanked scanline
* 10-ish colors per scanline completely disabled, if the jittery rainbow is acceptable. (Example: ccovell's "Sayoonara!" demo)
Quote:
3- Once rendering is turned off, the PPU's rendering logic gets a bit messed up, and sprites for example may need an entire scanline to fully recover
Not may, do. Sprites are loaded on the previous scanline, and if anything goes wrong during sprite evaluation they'll be wrong—at best "missing"—for the following scanline.


Top
 Profile  
 
PostPosted: Fri Jan 04, 2019 3:32 am 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 97
Location: Gothenburg, Sweden
tokumaru wrote:
Since you're messing with mid-screen palette changes, let me warn you this is a notoriously hard thing to get right on the NES for a few reasons:
...

My goal is to use this to change the palette for a top-screen status bar in a non-scrolling (so far) 2 player death match game. I have an idea of introducing scrolling later but for now, just changing the palette would do.

However, given your warning I realize I might have to abandon the idea in the end. But for now though, I'd like to just try to get it working one thing at a time in this little test rom.
Unfortunately I won't have a lot of time to play around with this today but maybe tomorrow. Until then, how do I set the y-scroll with writes to 2006?

Thanks for all the help guys! I really appreciate you all taking your time to help me understand all this.


Top
 Profile  
 
PostPosted: Fri Jan 04, 2019 9:02 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11376
Location: Rio de Janeiro - Brazil
I really don't advise you do mid-screen palette changes in your first project. It's harder to pull off correctly than most other things on the NES. Even most experienced programmers prefer not to do it due to the inherent complications.

As for setting the scroll mid-frame, it can mostly be done with 2 PPUADDR ($2006) writes, in the following format:

Code:
1st:     2nd:
0yyyNNYY YYYXXXXX

XXXXX: coarse X scroll (0 to 31);
YYYYY: coarse Y scroll (0 to 29);
NN: name table (0 to 3);
yyy: fine Y scroll (0 to 7);

This is almost the exact same format used when updating tiles on the name tables, with exception of the fine Y scroll bits that now go at the top.

Anyway, the problem is that there are 2 catches when using this register for scrolling purposes:

1- the top fine Y bit gets cleared on the first $2006 write, making it impossible to change the scroll to anywhere in the bottom half of a tile (i.e. only values 1 to 3 are possible);

2- $2006 doesn't affect the fine X scroll at all. Only a $2005 write can do that;

A carefully planned $2006/5/5/6 sequence can circumvent those problems, but if you're only using tile-aligned scroll values, those problems shouldn't affect you, and you can just do the regular $2006 writes.

EDIT: fixed the maximum coarse Y value, which is 29, not 30. It is in fact possible to set the scroll to rows 30 and 31, but that will cause the PPU to interpret attribute table data as if it were name table data.


Last edited by tokumaru on Sat Jan 05, 2019 1:46 am, edited 1 time in total.

Top
 Profile  
 
PostPosted: Fri Jan 04, 2019 3:53 pm 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 97
Location: Gothenburg, Sweden
tokumaru wrote:
I really don't advise you do mid-screen palette changes in your first project. It's harder to pull off correctly than most other things on the NES. Even most experienced programmers prefer not to do it due to the inherent complications.

You're probably right, but I must say the idea is still tempting. Perhaps it will be something I attempt in some sort of polish phase. If I opt for not having any scrolling at all, perhaps this could be a feature I would be able to pull off?

Whether I'll do something with this in my game or not, I've been learning from what you all have been writing so far and I'm still playing around with my test rom. I've managed to set the coarse Y scroll right by using the sprite-0 Y value divided by 8, though I seem to have to adjust it a bit by subtracting #2 before dividing. Otherwise the seam is never able to line up perfectly.

I also tried applying some fine Y scroll and it worked just as you explained. How would one go about solving the issue of that missing bit? Is it impossible on a standard UNROM mapper?

Oh, and how do I know when I'm in h-blank, so that I can make sure to only do my palette updates at that time?

I've added an updated test rom but with the fine y scroll turned off, as it seems to mess up the consistency of when the coarse scroll manages to line up seamlessly. You can use the d-pad to test x scroll and sprite-0 y position. Hold A for turbo speed. :beer:


Attachments:
sprite-0_test_03.nes [24.02 KiB]
Downloaded 77 times
Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 24 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 5 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