It is currently Thu Sep 20, 2018 6:47 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 22 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Tue Sep 11, 2018 8:48 am 
Offline
User avatar

Joined: Tue Jun 24, 2008 8:38 pm
Posts: 1954
Location: Fukuoka, Japan
I finally was able to make my code work with the mm3 and I can now split the screen like I intended. This is all great except one little thing that seems to appears but not in all situations: artifacts.

In one case, there is nothing at all and the screen seems connected together. In another one, there is a small defect where the screen is split and creates a line in some situation.

The last split requires a bank select since it shows text. Depending which way to switch (bg <-> spr or new bank) artifacts are different. Sometime adding nop improve or just move the problem elsewhere. Since the split contains tiles very close to the line, the artifact is more obvious.

What are the common cause of artifacts and is there some case where they are not avoidable? For now, my split is working so I'm not in rush to fix it but I would love to learn more on the subject. It gives it the "nintendo feel" (the defect) but if possible I want to avoid it :lol:


Top
 Profile  
 
PostPosted: Tue Sep 11, 2018 8:52 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2257
Location: DIGDUG
It's best to split where the entire scanline is the same color.

If that isn't possible, try to time the split to happen as near to the edge of the screen as possible.

If that isn't good enough, you could cover the glitch with sprites.

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


Top
 Profile  
 
PostPosted: Tue Sep 11, 2018 9:04 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 20562
Location: NE Indiana, USA (NTSC)
Background CHR bank switching can happen between scanlines without artifacts during a 76-pixel window from x=249 to x=324. This comprises the following:

  • x=249-256: The last 8 dots of a scanline, when the PPU reads the unused 34th tile of a scanline
  • x=257-320: The first 64 dots of horizontal blanking, when the PPU reads sprite tile data
  • x=321-324: The following 4 dots of horizontal blanking, when the PPU reads nametable for the next scanline's first tile

This window is roughly 25 CPU cycles wide on NTSC. On MMC3, for example, changing the entire $0000-$0FFF range on MMC3 needs three writes: $8000 before the window, and then $8001, $8000, and $8001 in the window.
Code:
ldx #$00
stx $8000  ; Select window: CHR $0000
inx
lda split_CHR_bank_0000
ldy split_CHR_bank_0800
; The writes must occur within the 76-dot window x=249-324,
; which is 25 cycles wide on NTSC/Dendy or 23 on PAL.
; Fortunately, the first and last writes span only 9 cycles.
sta $8001  ; Change window: CHR $0000
stx $8000  ; Select window: CHR $0800
sty $8001  ; Change window: CHR $0800


It probably won't be practical to fit both the last 2 writes of a $2006-$2005-$2005-$2006 scroll change sequence and the writes needed for CHR bank switching into this window. You'll have to blank the background for a scanline.


Top
 Profile  
 
PostPosted: Tue Sep 11, 2018 10:08 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10814
Location: Rio de Janeiro - Brazil
Clean splits are possible, it all comes down to timing. You have to familiarize yourself with when in each scanline the PPU performs its various tasks, so you know when it's safe to modify whatever you need to modify.

This diagram is a great reference of when stuff happens. During most of the scanline, the PPU is fetching pattern bytes in order to render the background, so if you want to switch BG pattern banks, you have to do it after the last BG pattern is fetched, and before the first one of the next scanline is. Sprite patterns are fetched between PPU cycles 261 and 320, so avoid switching sprite banks at that time. The scroll finishes auto updating at PPU cycle 257, so scroll changes should happen after that but before the background fetches resume at cycle 321.

I don't know how your IRQ handler is structured, but like I said before, MMC3 IRQs fire well into hblank, so there isn't any usable hblank time left to do raster effects the right away. You have to wait until the next hblank in order to do stuff at the correct time. While you wait, you can make use of the time to prepare data, write to registers that can be written in advance, etc. do that only the crucial writes are left for hblank. Keep in mind that to support PAL and NTSC in the same binary you'll have to compensate for the different scanline lengths (113.666 CPU cycles in NTSC, 106.5625 in PAL) when waiting for the next hblank.


Top
 Profile  
 
PostPosted: Tue Sep 11, 2018 7:50 pm 
Offline
User avatar

Joined: Tue Jun 24, 2008 8:38 pm
Posts: 1954
Location: Fukuoka, Japan
@dougeff

I guess this is as possibility if there is no way around it. thanks!

@tepple

A lot of interesting details! I guess if I cannot time it properly, I need to blank 1 scanline but how. Forgot about that ^^;;;

@Tokumaru

Since the first split is working fine (there is enough space between both part so no artifacts occurs), I will provide the code for the last IRQ, the bottom one, launched around 200:

Code:
;->BEGIN----------------------------------------------------------------------
;
; Title screen bottom screen irq
;
.proc subTitleBottomScreenIrqHandler

   saveRegisters

   lda #$01                  ; CHR 2K, second
   sta MMC3_CTRL
   lda #$1C
   sta MMC3_PAGE

   lda zpPpuCtrlFlags            ; load current flags
   and #%11111100               ; remove currently set NT values
   ora #PPU_CTRL_ADR_NT1         ; Set in NT2400 (%00000001) and
   sta PPU_CTRL               ; update PPU right away

   bit PPU_STATUS
   lda #0                     ; put scroll at begining
   sta PPU_SCROLL

   sta MMC3_IRQ_DISABLE         ; Acknowlege IRQ by writting any value in this register

   ; set next handler
   setDynamicRtiFunc subTitleMidScreenIrqHandler, rtiVectorPtr

   restoreRegisters

   rti

.endproc
;-<END------------------------------------------------------------------------


I guess I will need to learn how the mesen viewer works that linariq shown to me and that should help time it. The included pictures shows where the glitch occurs. As you can see, the schema between top and bottom part is very tight. This might be the reason why it glitches more easily. There is 1 line in the middle that shouldn''t be there.

Attachment:
the_glitch.png
the_glitch.png [ 335 Bytes | Viewed 477 times ]


Top
 Profile  
 
PostPosted: Tue Sep 11, 2018 9:42 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10814
Location: Rio de Janeiro - Brazil
Hum, it looks like your IRQ handler just performs the tasks straight away without attempting to time the register writes...

Consider the following: MMC3 IRQs fire at PPU cycle 260, 4 cycles into hblank. The CPU needs 7 cycles just to call the IRQ handler (that's another 21 PPU cycles), and since the CPU can't stop in the middle of an instruction, it may have to wait up to 7 cycles (another 21 PPU cycles) before calling the IRQ handler. Once inside the handler, you save the CPU registers (if you're using the stack, that's 3+2+3+2+3=13 CPU cycles, or 39 PPU cycles), and then there's 12 more CPU cycles (36 PPU cycles) for the CHR switch, which means it will take place between PPU cycles 356 and 377, but since an scanline has only 341 cycles, that switch already happens during the visible part of the next scanline, which's not good if the tiles being switched are used in that scanline.

Anyway, your handler is around pixel 15 of the next scanline already when you start to change the scroll. I'm gonna stop counting cycles now, since we can see that the scroll will be changed somewhere in the middle of the visible scanline. According to The Skinny on NES Scrolling, your $2000 and $2005 writes affect mostly the temporary VRAM register (commonly referred as "t"), which doesn't cause any visible side effects, but the lower 3 bits written to $2005 affect the fine X scroll immediately, causing the rest of the scanline to be shifted up to 7 pixels.

Like I said before, if you want clean splits, you can't switch banks or change the scroll at random times, you have to use timed code in the IRQ handler to make sure that all changes happen at safe times. In this specific case, you could keep most of the IRQ handler as is, but don't do the sta MMC3_PAGE or the sta PPU_SCROLL - buffer these values in different registers (say, X and A), and add timed code to wait for the next hblank, when you can safely do:

Code:
stx MMC3_PAGE
sta PPU_SCROLL


Top
 Profile  
 
PostPosted: Tue Sep 11, 2018 10:02 pm 
Offline
User avatar

Joined: Tue Jun 24, 2008 8:38 pm
Posts: 1954
Location: Fukuoka, Japan
You are right, the handler just do the tasks without considering when it should be done. The reason behind that is it's my first time doing a split event with multiple IRQ and never had the need for timed code. Actually, I never had to write timed code up to now ;)

From you message, I'm learning that CPU/PPU cycles are not the same length. Wasn't aware of that too! That's really important to understand what to do next.

A complete frame consist of 3 steps:

- Vblank set the original bank and adjust top scroll position. Request IRQ
- First IRQ just scroll at specific location (no bank here)
- Last IRQ switch to specific banks for fonts and go specific location (adjacent NT)

The last one will requires timed code. From what I understand, what I need to do is:

- First, I put both values for MMC3 inside register
- I need time with nop (?) to reach next hblank
- switch bank
- scroll to proper location

I just need to figure out how many nop I need to get to the next hblank. You said that scroll doesn't affect visuals. Should I scroll first, time to hblank then switch bank instead?

I think I'm getting close to get rid of that glitch. A lot of fun with that little thing :) Thanks again!

edit:

I experimented on my own to learn more about it. I decided to put the scroll first, time code then bank and now it is fine!

For some reason, if I timed bank first, time, then scroll there was new artifact that occurred later when a scroll occurred in the middle section. I'm on the right way to make it works!


Top
 Profile  
 
PostPosted: Tue Sep 11, 2018 10:58 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10814
Location: Rio de Janeiro - Brazil
Banshaku wrote:
You are right, the handler just do the tasks without considering when it should be done. The reason behind that is it's my first time doing a split event with multiple IRQ and never had the need for timed code.

Writing all the tasks without regard for timing is a perfectly valid first step.

Quote:
From you message, I'm learning that CPU/PPU cycles are not the same length. Wasn't aware of that too!

The CPU is 3 times slower than the PPU in NTSC, and 3.2 times slower in PAL. For example, in NTSC, while the CPU runs a CLC instruction, the PPU draws 6 pixels.

Quote:
I just need to figure out how many nop I need to get to the next hblank.

Each scanline is 341 ppu cycles, 256 of which output pixels, and 85 are technically hblank. Start counting cycles from when the IRQ handler starts (don't forget the 7 CPU cycles it takes to call the handler) and you should have an idea. Also, Mesen has a ton of tools to let you know when things are happening, don't forget that.

Quote:
You said that scroll doesn't affect visuals.

No, I said that COARSE X scroll (bits 3-8) changes don't affect visuals (they're buffered in the temporary VRAM address register), but FINE X scroll (bits 0-2) changes take effect immediately, so you definitely want the $2005 write to happen during hblank.

Quote:
Should I scroll first, time to hblank then switch bank instead?

I'd say do the switch first, the scroll second, like in the short piece of code I wore above. Keep in mind that the MMC3_CTRL write can happen in advance, and so can the PPU_CTRL write, so you literally only need those last 2 writes I showed above to happen during hblank, which greatly increases your chances of timing things right.

The fact that PAL and NTSC scanlines have different amounts of CPU cycles makes it a bit annoying to time raster effects, but since the difference is about 7.1 cycles, the timing-sensitive register writes take at most 5 cycles (a write effectively happens during the last cycle of a store instruction), and the hblank window is about 20 cycles, you may actually be able to get away with using the same timed code for both PAL and NTSC. I personally prefer to make the wait code adjust itself, so that I have more usable hblank time. You can put something like this as part of your wait code:

Code:
  lda ConsoleIsNTSC ;test region (detected during initialization)
: bpl :+ ;skip if PAL
  lsr ;waste more time if NTSC
  bpl :-
:

The code above will run in 6 cycles if PAL, 13 if NTSC, compensating for the 7-cycle difference.


Top
 Profile  
 
PostPosted: Wed Sep 12, 2018 12:23 am 
Offline
User avatar

Joined: Tue Jun 24, 2008 8:38 pm
Posts: 1954
Location: Fukuoka, Japan
Regarding your example, I didn't realize at all that others were already prepared (i.e. some can be done in advance) and was sure that I had to do them consecutively ^^;; For now I did the scroll before and it is "seems" to be working well but I will change like you mentioned since it could be just lucky that it's working. It's not an intensive screen so maybe it was kind of fine.

Now that I'm thinking on the subject and hbank, if I have one condition where I would like to change 1 color only, could I change that color in hblank with timed code?

I learned a lot of small things that I was not aware of, thanks!


Top
 Profile  
 
PostPosted: Wed Sep 12, 2018 3:15 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10814
Location: Rio de Janeiro - Brazil
Changing colors is tricky... The 21-cycle window of hblank is too short to do what has to be done:

1- set the PPU address to point to the color you want to change;
2- write the color;
3- restore the scroll;

And even if 21 cycles was enough for all that, I don't think you can write to the palette while the PPU is reading from the pattern tables (i.e. fetching sprite patterns), so you probably have to turn rendering off to do it, which increases the time the whole process takes even more.

To do things completely cleanly, I think you need at least 1 blank scanline (i.e. with rendering disabled) in order to change the palette mid-frame, but more scanlines will be needed if you need to change more colors, because even though rendering is disabled and you have unrestricted access to VRAM, the PPU has a "feature" that causes the color pointed by the VRAM address register to be rendered on screen instead of color 0, so you'd get a rainbow effect if you updated colors during the visible part of the scanline.

There is one Indiana Jones game that changes colors mid-frame to create a gradient effect in the title screen, without blank scanlines. I don't know if the effect is completely stable on hardware, but if you really want to pursue this, you definitely should check that game out.

I've personally put mid-screen palette changes in my list of things to avoid. Too hard to pull off, lots of restrictions and drawbacks.


Top
 Profile  
 
PostPosted: Wed Sep 12, 2018 10:43 am 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 7532
Location: Seattle
tokumaru wrote:
so you definitely want the $2005 write to happen during hblank.
That depends.

For a 2006/2005/2005/2006 scroll, the one in red should happen during hblank.
For just a single-2005 X scroll change, it must happen before hblank, because coarse X is copied over on the very first pixel of hblank. If it happens after hblank starts, the entire following scanline will use the wrong coarse X for the fine X.

The entire reason for the difference is that final $2006 write, which copies over the coarse X again, as well as the coarse Y.


Top
 Profile  
 
PostPosted: Wed Sep 12, 2018 12:31 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10814
Location: Rio de Janeiro - Brazil
lidnariq wrote:
For just a single-2005 X scroll change, it must happen before hblank, because coarse X is copied over on the very first pixel of hblank.

You're absolutely right! Sorry for giving out wrong information. Guess I'm so used to the $2006/5/5/6 method that I forgot we had to rely on the PPU copying t to v.

The window for a X-scroll-only update is pretty narrow then, because if you do it too early, you get the wrong fine X scroll for the rest of the scanline, but if you do it too late, the coarse X scroll won't change until the next scanline. Looks like you can't avoid a little glitching near the end of the scanline if there are solid pixels there when using only $2005.

Quote:
The entire reason for the difference is that final $2006 write, which copies over the coarse X again, as well as the coarse Y.

That's probably why I always go with the $2006/5/5/6 method, even when changing only the X scroll. That method I know can be implemented without glitches.

If you use the $2006/5/5/6 method, where only the last 2 writes must take place during hblank, plus the final MMC3 CHR switch write, that's still only 9 cycles to fit in a 21-cycle window. 12 cycles of wiggle room is still comfortable enough.


Top
 Profile  
 
PostPosted: Wed Sep 12, 2018 5:54 pm 
Offline
User avatar

Joined: Tue Jun 24, 2008 8:38 pm
Posts: 1954
Location: Fukuoka, Japan
@linariq

I was like "why even when I follows people sample and put the scroll in hblank (mesen tells me it's there) I still find a way to not make it work? I'm still a nes noob, I guess :cry: Will check later, will leave the scroll before hblank for now because it 'seems' to work. why... hmm...". I feel so much better now :lol:

Thanks for clarifying that part!

@tokumaru

I guess the nes scroll is a very complicated topic that is easy to mix it up (I mix many nes concept all the time, I'm the king of that for sure!). Now I learned many new things! Next time I should ask again why it fail even though it is done like mentioned. It would have brought that issue faster.

As for color swap, I only need to change 1 color for a period of a ~2 seconds animation where there is no scroll so in the middle split, even if I need to blank one scanline that would be acceptable with no artifact. Changing that color would be faster to test than reproducing the affected part with sprites which would requires a lot of manual/editing work.

My only problem is, how do you blank for 1 scanline? My guess would be you stop the PPU (bg/sprite), do your work in less than 260 cycles and re-activate it?


Top
 Profile  
 
PostPosted: Wed Sep 12, 2018 6:08 pm 
Offline

Joined: Sun Feb 07, 2016 6:16 pm
Posts: 516
Banshaku wrote:
I was like "why even when I follows people sample and put the scroll in hblank (mesen tells me it's there) I still find a way to not make it work? I'm still a nes noob, I guess :cry: Will check later, will leave the scroll before hblank for now because it 'seems' to work.
AFAIK, the X scroll updates need to be done before cycle 257 for them to be applied to the next line, don't they? At least, this timing diagram says the coarse X scrolling bits are copied at exactly cycle 257, and nowhere else, so I would imagine changing it just after cycle 257 will result in a whole scanline of delay for the coarse X scroll offset to be changed?

(Keep in mind that I'm usually mostly clueless about programming for the NES, so I might be forgetting something, but at the very least, looking at Mesen's code, this looks like it would be the case)

Edit: I should probably mention that I didn't read the whole thread, and also this only applies if you only write to $2005 - writing to $2006 before cycle ~320 during hblank should work properly for both X and Y scroll offsets?


Top
 Profile  
 
PostPosted: Wed Sep 12, 2018 6:39 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10814
Location: Rio de Janeiro - Brazil
Yeah, it was my mistake to say that the scroll change should happen during hblank. That's true for $2006/5/5/6, but not $2005 alone. X scroll changes via $2005 only definitely must happen before the end of the scanline, so the PPU can copy the X bits from t to v at cycle 257.

As for color changes, I just avoid them altogether, too much stuff to watch out for. If scroll changes seem complicated, wait until you try palette changes! :twisted: I don't have many advices regarding that, since I could never do it in a satisfactory way myself. Disabling rendering mid-frame is tricky when there are sprites in the scanline where rendering is turned off... If not timed right, sprites can get corrupted. And even with rendering turned off, the actual color replacement must still happen during hblank, or you'll get color artifacts on screen. And once everything is done, you need to reset the scroll with a combination of $2006/5 writes ($2005 alone won't cut it after you've modified the PPU address to write to the palette area.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 22 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 1 guest


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