Increment PPU address by a number

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

User avatar
kikutano
Posts: 115
Joined: Sat May 26, 2018 6:14 am
Location: Italy

Increment PPU address by a number

Post by kikutano »

Hello to everyone, I'm creating a new prototype for NES. I've an "action space matrix" where the player can move in 4 directions as you can see in the image.

Image

I need to change the background into the matrix at runtime with some wall sprites. The matrix PPU address start at 2103 and it end at 2376. I created a loop to change the background tile by tile, row by row:

;2103 -> 2116
;2120 -> 2136
;...
;2303 -> 2376

Code: Select all

generate_random_walls:
  lda $2002             
  lda #$21
  sta $2006             
  lda #$03
  sta $2006             

  ldx #$00
  ldy #$00
.gen_random_walls_loop:
    lda #$C2
    sta $2007
    inx 
    cpx #$14
    bne .gen_random_walls_loop

    .go_next_row:
when the loop goes to #$16 on the X register I need to "jump" from $2116 to $2120 and continue to write on PPU that auto increment by one every time. So, what's the best way to do that? I've to reset the address in this way at next row? Or there is a more efficent way?

Code: Select all

lda $2002             
  lda #$21
  sta $2020             
  lda #$03
  sta $2006             
Thanks a lot! :)
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Increment PPU address by a number

Post by tokumaru »

You should keep the base address as a variable in RAM, as opposed to hardcoding it in the logic, so you can modify it as you move down the screen. For example:

Code: Select all

  ;initialize the address for the first row
  lda #$03
  sta ScreenAddress+0
  lda #$21
  sta ScreenAddress+1
Then at the start of every row you can set the address from this variable:

Code: Select all

  ;start the row
  lda ScreenAddress+1
  sta $2006
  lda ScreenAddress+0
  sta $2006
And at the end of every row, you increment the address by 32 (the number of times in a full row) to move one row down:

Code: Select all

  ;move one row down
  clc
  lda ScreenAddress+0
  adc #$20
  sta ScreenAddress+0
  lda ScreenAddress+1
  adc #$00
  sta ScreenAddress+1
User avatar
NOOPr
Posts: 75
Joined: Tue Feb 27, 2018 10:41 am
Location: Brazil
Contact:

Re: Increment PPU address by a number

Post by NOOPr »

This should do the trick (untested code!!)

Code: Select all

PPUSTATUS	= $2002
PPUADDR		= $2006
PPUDATA		= $2007
START_ADDR	= 2103 
MATRIX_ROWS	= 10 ; arbritrary value
MATRIX_COLS	= $13
TILE		= $c2

generate_random_walls:
	lda PPUSTATUS             
	lda #>START_ADDR
	sta PPUADDR         
	lda #<START_ADDR
	sta PPUADDR           

	ldy #MATRIX_ROWS
	lda #TILE
gen_rows:
	ldx #MATRIX_COLS
gen_cols:
	sta PPUDATA
	dex
	bne gen_cols
	ldx #(32-MATRIX_COLS)
skip_col:
	bit PPUDATA
	dex
	bne skip_col
	dey
	bne gen_rows
EDIT: changed 1st line after skip_col label
Last edited by NOOPr on Thu Aug 15, 2019 9:08 am, edited 1 time in total.
User avatar
kikutano
Posts: 115
Joined: Sat May 26, 2018 6:14 am
Location: Italy

Re: Increment PPU address by a number

Post by kikutano »

Thanks a lot, I will try as soon as I can. :)
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Increment PPU address by a number

Post by tokumaru »

Cool! Skipping the remaining positions is definitely an elegant approach in this case!
User avatar
NOOPr
Posts: 75
Joined: Tue Feb 27, 2018 10:41 am
Location: Brazil
Contact:

Re: Increment PPU address by a number

Post by NOOPr »

tokumaru wrote:Cool! Skipping the remaining positions is definitely an elegant approach in this case!
The first time I saw this bit $2007 to increment $2006 was in a reply from you on a topic about change palette mid frame
User avatar
kikutano
Posts: 115
Joined: Sat May 26, 2018 6:14 am
Location: Italy

Re: Increment PPU address by a number

Post by kikutano »

NOOPr wrote:This should do the trick (untested code!!)

Code: Select all

PPUSTATUS	= $2002
PPUADDR		= $2006
PPUDATA		= $2007
START_ADDR	= 2103 
MATRIX_ROWS	= 10 ; arbritrary value
MATRIX_COLS	= $13
TILE		= $c2

generate_random_walls:
	lda PPUSTATUS             
	lda #>START_ADDR
	sta PPUADDR         
	lda #<START_ADDR
	sta PPUADDR           

	ldy #MATRIX_ROWS
	lda #TILE
gen_rows:
	ldx #MATRIX_COLS
gen_cols:
	sta PPUDATA
	dex
	bne gen_cols
	ldx #(32-MATRIX_COLS)
skip_col:
	bit PPUDATA
	dex
	bne skip_col
	dey
	bne gen_rows
EDIT: changed 1st line after skip_col label
This code works great! Thanks a lot!
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Increment PPU address by a number

Post by rainwarrior »

Reading $2007 to increment the address is useful, but usually only for a small number of increments in a row. Each BIT $2007 is 4 cycles, so that "small number" might be 4 or 5.

In that loop with DEX/BNE that's 9 cycles each, so it becomes slower after only 3 loops or so. It's probably a good solution for small code (or simple code), but if you find yourself needing to squeeze more into your vblank it's maybe not going to be the best option.


Alternatives:

If start is the starting address of your update, you can jump to an arbitrary offset from it in 22 cycles. Consider that you don't have to write this result back to start: you can do all the skips relative to the start, rather than the current position, which can probably save a few cycles.

Code: Select all

lda start+0
clc ; can potentially omit this for another -2 cycles if careful
adc #<offset
tax
lda start+1
adc #>offset
sta $2006
stx $2006
Also, if you want to increment by 32 specifically you could write to $2000 to switch increment mode then switch back. This seems to cost 16 cycles already, so unless you're looking for 32 or 33 specifically it's probably better to just go through $2006.

Code: Select all

lda #%10000100 ; +32 increment mode
sta $2000
bit $2007 ; skip 32
lda #%10000000 ; +1 increment mode
sta $2000
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Increment PPU address by a number

Post by tokumaru »

Since this code appears to be drawing the entire board, I assume it's not running during vblank, but during the initialization of the level. If this is the case, the time spent BIT'ing $2007 is irrelevant, making the simplicity and compactness of the code more desirable IMO.

During vblank though, where every cycle counts, it indeed only makes sense to use BIT if that's faster than the alternative.
User avatar
kikutano
Posts: 115
Joined: Sat May 26, 2018 6:14 am
Location: Italy

Re: Increment PPU address by a number

Post by kikutano »

This code will run only once at change of the room. So it's not so critical. But thanks, I will try to study your code anyway. :)
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Increment PPU address by a number

Post by tokumaru »

Just to make it clear: I'm saying that NOOPr's code looks pretty good for this particular situation, more elegant and compact than what I had suggested. IMO you should stick with it.
User avatar
kikutano
Posts: 115
Joined: Sat May 26, 2018 6:14 am
Location: Italy

Re: Increment PPU address by a number

Post by kikutano »

Hello again, I'm using your tips to implement creation of the matrix, ( 16x16 tiles ), everything works great when I start the game and i generate it with this code:

Code: Select all

gen_level_0_room_0:
  ldy #MATRIX_ROWS

  gen_rows:
    ldx #MATRIX_COLS
    
    gen_cols:
      
      ;jsr generate_random_tile  

      lda #$D0
      sta PPUDATA

      dex
      bne gen_cols
      ldx #( 32 - MATRIX_COLS ) ;32x30 tiles
      
      skip_col:
        bit PPUDATA
        dex
        bne skip_col
        dey
        bne gen_rows 

  rts
For now I'm putting the tile "#$D0". The problems start when I want to re-generate the matrix to change the room, for some reason it start to glitch probably because i'm putting too many cycles into vblank? What's the best pratices to update the background matrix once when the player it the door? In my previous game I can change the entire background without glitch, but I try to update a portion of the screen, it glitch.

Any idea? Thanks a lot!
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Increment PPU address by a number

Post by tokumaru »

For large PPU updates you have to turn rendering off, so you have free access to VRAM, and turn it back on once you're done. There are a couple of things you should keep in mind though:

1- Wait for vblank (via the NMI handler, not the "buggy" $2002 vblank flag) before turning rendering off, to avoid turning it off mid-screen and causing a brief visual glitch;

2- Take precautions to prevent the NMI handler from interfering with your large PPU updates. You should either have a flag indicating whether the NMI handler is allowed to touch the PPU, or disable NMIs altogether at the same time you disable rendering, then enable NMIs again along with rendering when you're done updating. If you do disable NMIs, you won't be able to play sounds or music during these transitions.
User avatar
kikutano
Posts: 115
Joined: Sat May 26, 2018 6:14 am
Location: Italy

Re: Increment PPU address by a number

Post by kikutano »

Thanks. Another solution could be to create a "fade in" effect that draw one column at time every t interval. :D

But, my question is, how Zelda room transition works? It scroll the entire screen without problem.
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Increment PPU address by a number

Post by rainwarrior »

kikutano wrote:It scroll the entire screen without problem.
You don't see a whole new screen in a single frame. You can spread out the changes across several vblanks.

If you want to see what Zelda does, open the nametable viewer in FCEUX or Mesen and go frame by frame as you pass to the next room. You can watch the whole process.
Post Reply