This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Midscreen scroll/parallax/character bank change glitching?

Midscreen scroll/parallax/character bank change glitching?
by on (#108467)
I'm trying to remove the glitching I see at the edges of scanlines I see when setting scroll values
mid hblank in my game. I was wondering if you guys had any suggestions, I included my irq routine and an explanation of how it works.

I included my mmc3 irq routine (multiLineIrq: is the entry point) to see if any of you could provide some pointers as to how I can fix this.

The basic jist is that there is are 4 tables of 2006,2005,2005,2006 for the register writes that store my scroll values for each of the N mmc3 scanline irqs (indexed by the number of irqs hit so far per screen).

IRQ_SCROLL_2006_FIRST
IRQ_SCROLL_2005_Y
IRQ_SCROLL_2005_X
IRQ_SCROLL_2006_SECOND

There is a table of # line deltas between irqs: R_MMC_IRQ_LINE0

Nametable layout:
Foreground layer: page 0
Background layer: page 1
Status bar: last 2 character rows of page 1


Here is what happens per frame:

24 lines (status bar size) after vblank ends:

The first irq set is change part of the palette from gray (for the status bar) to the colors used on the play screen. Also the character set is changed to a blank page to blank the screen and remove some glitching.

2 lines later

The second irq sets the 2006,5,5,6 for the playscreen (can be parallaxing background or foreground), and sets the character set for the playscreen. During the 2006,5,5,6 setting background drawing using the ppumask is disabled to ensure the scroll registers are set properly. This is also where sprite drawing gets enabled for the first time for the screen.

The next (N-3) irqs set 2006,5,5,6 for the different bands of scrolling foreground/background "layers". Also there is an irq for a Crystalis style single screen mirroring.

The final irq depends on the mode.
In play mode about 8 lines above the visible screen bottom, the final irq changes the character set to a blank page to blank the screen and remove some artifacts.

In conversation mode the final irq changes the character set to the alphanumeric font pages, the first 3 colors to grays for the font rendering, and changes the sprite bank to the portrait.

-Sedwave / Hiatus Ward ( http://www.nesworld.com/homebrew/hiatwrd0.zip)

Code:
; ===============================================
endPaletteIrq:
    pla
    tay   ;pull y
    pla
    tax   ;pull X
    pla   ;pull a
    plp   ; pull flags
    rti

; ===============================================
setBlankingAndFinishIrq:
    setMMC3ChrBankLo 0, #K_EMPTY_CHR_PAGE
    setMMC3ChrBankLo 1, #K_EMPTY_CHR_PAGE
    setMMC3ChrBankLo 2, #K_EMPTY_CHR_PAGE
    setMMC3ChrBankLo 3, #K_EMPTY_CHR_PAGE
    jmp endIrq


; ===============================================
setPaletteIrq:
    lda R_MMC_IRQ_LINE0 +1   ; setup next irq
    setMMC3ScanlineIrq;
    inc R_MMC_NUM_IRQ_HIT

    nop
    nop
    nop
    nop
    nop

    jsr setPlayScreenPaletteIrq

    ;blank screen but still allow mmc3 irq counter by setting to empty character page
    setMMC3ChrBankLo 0, #K_EMPTY_CHR_PAGE
    setMMC3ChrBankLo 1, #K_EMPTY_CHR_PAGE
    setMMC3ChrBankLo 2, #K_EMPTY_CHR_PAGE
    setMMC3ChrBankLo 3, #K_EMPTY_CHR_PAGE

    lda #(PPU_MASK_SPR_VIS | PPU_MASK_BKG_VIS )
    sta PPU_MASK

    jmp endPaletteIrq

;note split irq does not turn off ppu
setSplitIrq:
    dex

    lda IRQ_SCROLL_2006_FIRST, x
    sta PPU_ADDR
    lda IRQ_SCROLL_2005_Y, x
    sta PPU_SCRL
    lda IRQ_SCROLL_2005_X, x
    sta PPU_SCRL
    lda IRQ_SCROLL_2006_SECOND, x
    sta PPU_ADDR
    jmp endIrq

; ===============================================
; Interrupt handlers
; ===============================================
irq:
multiLineIrq:
    php   ;push flags
    pha   ;push a
    txa
    pha   ;push x
    tya
    pha   ;push y

    lda #0   
    sta MMC3_IRQ_ACK

    ldx R_MMC_NUM_IRQ_HIT
    beq setPaletteIrq
    cpx R_CONVO_IRQ_NUM
    bne notConvoIrq
    jmp convoIrq
notConvoIrq:

    inx
    cpx R_MMC_NUM_IRQ
   
    bne @notFinalIrq
    jmp setBlankingAndFinishIrq
@notFinalIrq:

    lda R_MMC_IRQ_LINE0, x       ; accum contains number of scanlines until next irq, setup next irq
    setMMC3ScanlineIrq;

@noNewIrq:

    ;how many irq previously hit   
    ldx R_MMC_NUM_IRQ_HIT
    cpx R_SPLIT_IRQ_NUM
    beq setSplitIrq
    dex  ;  irq #1 index 0, irq#2 index 1, etc...

    ; between 2 and x nops stops shaking
    ; 2 seems best on real hardware
    nop
    nop

    ;note, setting scroll value to different X value midscreen requires PPU
    ; drawing to be disabled so that internal X counter does not interfere with
    ; setting scroll address and values
    lda #PPU_MASK_SPR_VIS
    sta PPU_MASK
postBlankIrq:
    lda IRQ_SCROLL_2006_FIRST, x
    sta PPU_ADDR
    lda IRQ_SCROLL_2005_Y, x
    sta PPU_SCRL
    lda IRQ_SCROLL_2005_X, x
    sta PPU_SCRL
    lda IRQ_SCROLL_2006_SECOND, x
    sta PPU_ADDR

    lda #(PPU_MASK_SPR_VIS | PPU_MASK_BKG_VIS )
    sta PPU_MASK

    ldx R_MMC_NUM_IRQ_HIT
    cpx #1
    bne noSetupPlayscreenChar
    setMMC3ChrBankLo 0, LEVEL_DATA_CHR_PAGE_0
    setMMC3ChrBankLo 1, LEVEL_DATA_CHR_PAGE_1
    setMMC3ChrBankLo 2, LEVEL_DATA_CHR_PAGE_2
    setMMC3ChrBankLo 3, LEVEL_DATA_CHR_PAGE_3
    nop
    nop
    nop
    nop
    nop

noSetupPlayscreenChar:
endIrq:
    inc R_MMC_NUM_IRQ_HIT

    pla
    tay   ;pull y
    pla
    tax   ;pull X
    pla   ;pull a
    plp   ;pull flags

    rti
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108473)
sdwave wrote:
Code:
; ===============================================
; Interrupt handlers
; ===============================================
irq:
multiLineIrq:
    php   ;push flags

...

    plp   ;pull flags

    rti

Not related to your actual question (I don't have time to go through the code right now), but a small detail: you don't need to push and restore the flags, 6502 will do that automatically.
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108476)
I haven't looked at the source, but I have something I'd like to comment:

sdwave wrote:
During the 2006,5,5,6 setting background drawing using the ppumask is disabled to ensure the scroll registers are set properly.

There's no need to disable background rendering if you make sure that the last $2006 write happens during HBlank. In fact, disabling rendering only increases the chances of visual glitches, since you have more instructions to execute during HBlank. When you use the $2006/5/5/6 trick for resetting the scroll, only the last 2 writes have to fall inside the horizontal blank, because the first 2 don't affect rendering at all. So get rid of the disabling/enabling of the background on scroll changes and make sure that the last 2 scroll writes take place during HBlank (that's 8 cycles to fit in a window of 28 cycles, it's not hard at all).
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108477)
Also, you should really write to
$2005, $2005, $2005, $2006 to make your life easier (I think). This way you only have to write 3 different values instead of 4 :

$2005 := HScroll
$2005 := VScroll
$2005 := HScroll
$2006 := (VScroll << 3) | ((HScroll >> 3) & 0x7)
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108478)
You need the first $2006 write to set the name table.
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108479)
No - a write to $2000 can set the nametable if necessarily (and in many cases this might not be necessarily). That's what the $2000 register was originally meant for, folks.
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108480)
Bregalad wrote:
No - a write to $2000 can set the nametable if necessarily (and in many cases this might not be necessarily). That's what the $2000 register was originally meant for, folks.

Sure, and during normal scroll operation I always recommend that $2000 be used, but since this is the tricky version of setting the scroll, why put an extra write that will just increase the time it takes to fully update the scroll? I mean, you can do it with 4 writes, why use 5? There's no real advantage, as forming the first $2006 isn't complicated at all... Only the name table bits need to be correct in that byte, the rest will be overwritten by the following writes.
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108482)
There is a real advantages as the values can be precalculated in A, X, and Y and be updated in such a way that :
Code:
   sta $2005
   stx $2005
   sta $2005
   sty $2006

(for example)
Also, exept for the last $2006 writes, you write directly the scroll value you want and no extra calculations are required, which is a clear advantage.

On the other way I see no point in doing a 6, 5, 5, 6 write, which is overcomplicated and has no real benefit over (0), 5, 5, 5, 6.
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108483)
Bregalad wrote:
There is a real advantages as the values can be precalculated in A, X, and Y

Since only the last 2 writes are under timing constraints, this is of very little relevance. You can't preload the $2000 value just like you can't preload the first $2006 value, so you still need 4 values (of which only 3 can be preloaded), no matter the method.

Quote:
Also, exept for the last $2006 writes, you write directly the scroll value you want and no extra calculations are required

The same goes for the 6/5/5/6 way. Like I said in the previous message, only the name table bits must be correct in the first $2006 write, because the rest is overwritten by the subsequent writes. The trouble of shifting the name table bits into a $2006 byte is nearly the same as that of pushing them into a $2000 byte, you just need a couple more shifts ($2000 has the name table bits at %------NN while $2006 needs them at %----NN--). That's 2 more bytes of code and 4 cycles, which is still less than the 3 bytes of the extra STA $2000, that also takes 4 cycles.

Quote:
On the other way I see no point in doing a 6, 5, 5, 6 write, which is overcomplicated

Do you really find ASL ASL complicated? It's actually simpler, since none of the other bits matter and you don't even need to clear A before shifting the name table values in. To form a proper $2000 byte on the other hand, you need to make sure that the upper bits contain correct PPU configuration values (pattern table addresses, sprite size, NMI...).

Quote:
and has no real benefit over (0), 5, 5, 5, 6.

It's faster. Not by much, but it is. It's the 0, 5, 5, 5, 6 way that offers no benefit over the 6, 5, 5, 6 way.

I'm not saying that you shouldn't use the longer method, if that's the one you're comfortable with, go for it! But saying that it's objectively better than the faster method and calling that one overcomplicated is bogus!
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108484)
Perhaps, but the point is, changing the scroll value multiple times in a row and rely on the last one to be used is dirty, and usually leads to more graphical glitches than using consistent values.

Quote:
Do you really find ASL ASL complicated?

Yes, it almost made my head explode ! :mrgreen:
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108486)
Writing to $2000 requires having the other bits correct (background pattern table base address, sprite size and pattern table base address if 8x8, and NMI). Writing to $2005/first needs to be done in horizontal blank if the fine X scroll ($2005/first bits 2-0) will be changing at all. So I'm with tokumaru: $2006 first is probably the best option.
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108493)
tepples wrote:
Writing to $2005/first needs to be done in horizontal blank if the fine X scroll ($2005/first bits 2-0) will be changing at all.

Oh yeah, I didn't even catch that. This means that all 4 writes would have to fall within HBlank, so that's 16 cycles to fit into 28 (as opposed to just 8 with the other method)... Still doable of course, but why would you want to reduce the amount of time you have left to perform other effects (like color emphasis, for example).
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108502)
Quote:
24 lines (status bar size) after vblank ends:

The first irq set is change part of the palette from gray (for the status bar) to the colors used on the play screen. Also the character set is changed to a blank page to blank the screen and remove some glitching.


How do you detect the end of the status bar if the first IRQ is set *after* it?

If you're using timed code then that's likely to be the source of the jitter, as DPCM sample playback can really screw up your timing.
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108737)
Quote:
When you use the $2006/5/5/6 trick for resetting the scroll, only the last 2 writes have to fall inside the horizontal blank, because the first 2 don't affect rendering at all.


Thank you! This right here was the magic I needed and I managed to get rid of my scanline glitches due to scrolling/ fg/bg switch on both actual hardware and Nestopia. I still have the rare issue that a DPCM operation can affect my timing and push the writes outside of hblank, but since I now have the cycles to do turn PPU on/off around the critical section, the rare DPCM operations during my timed code don't cause a scroll jump. Here is the change:

Code:
; setup and writes which can occur outside hblank
  lda IRQ_SCROLL_2006_FIRST, x
  sta PPU_ADDR
  lda IRQ_SCROLL_2005_Y, x
  sta PPU_SCRL
  lda IRQ_SCROLL_2005_X, x
  tay
  lda IRQ_SCROLL_2006_SECOND, x
  tax

; Note, setting scroll value to different X value midscreen requires PPU
; drawing to be disabled so that internal X counter does not interfere with
; setting scroll address and values.
; (Note this will only occur when DPCM operations interfere with timing and )
   lda #PPU_MASK_SPR_VIS
   sta PPU_MASK
   sty PPU_SCRL
   stx PPU_ADDR
   lda #(PPU_MASK_SPR_VIS | PPU_MASK_BKG_VIS )
   sta PPU_MASK



Quote:
Not related to your actual question (I don't have time to go through the code right now), but a small detail: you don't need to push and restore the flags, 6502 will do that automatically.


Good advice, that bought me a little more time and flexibility!


Quote:
How do you detect the end of the status bar if the first IRQ is set *after* it?


What I meant to say what when the first irq is triggered, the code within the irq sets..
The only timed code is within the irq for delaying until the hblank is hit. DPCM can in fact still interfere with this timing, but its a lot more rare.
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#108751)
sdwave wrote:
Code:
   lda #PPU_MASK_SPR_VIS
   sta PPU_MASK

You shouldn't need this (i.e. the comment about the need to disable drawing is not correct). If your scrolling breaks without this, maybe the $2005/6 writes are happening too early.
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#109298)
In case it hasn't been mentioned, changing the vscroll (which requires setting loopy_v), switching banks, or doing other stuff that affects rendering "immediately" probably can't be done without glitching during the last 20 ticks or so of hblank, as the first two tiles for the next scanline are fetched there - so the safe window might be slightly narrower than you expect.

The diagram I made in viewtopic.php?f=3&t=9901&start=30 might clarify this.
Re: Midscreen scroll/parallax/character bank change glitchin
by on (#109311)
x=256-320 is still plenty of window. Only the last two writes absolutely have to be within the window, and there are 5 cycles (15-16 dots) from the beginning of one write to the end of the second, that still leaves 48 dots, or 15-16 cycles.