Why is My Custom Nametable Rotated 90 Degrees Clockwise?

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

Post Reply
justin-rwx
Posts: 12
Joined: Sun Jan 10, 2021 1:12 pm

Why is My Custom Nametable Rotated 90 Degrees Clockwise?

Post by justin-rwx » Sat Feb 06, 2021 11:29 pm

I'm working through the Advanced Nerdy-Nights tutorial #3 regarding horizontal scrolling. In the fifth example, the background shows a SMB-like level as it scrolls horizontally behind a Mario sprite. My goal is to create my own background and have it scroll in the background.

Image

Using Tile Studio, I created a small 32 tile x 32 tile background with a large '3' in the background made up of small '3' tiles as shown below.

Image

I confirmed the shape of the hex data using the program HxD as shown below

Image

The problem is, when changing the source file in the code to point to my background, the background is loaded 90 degrees clockwise. Instead of a 3 in the background, it's more like a W. The small red square at the top right of the screenshot should be the first piece of data loaded into $2007, and the data should be loaded in the direction of the arrow I believe.

Image

I have a feeling it may be in the way that the nametable is loaded, but I can't seem to pinpoint why it would be loading the bottom left part of the binary file into $2000. Any thoughts?

The full code to scrolling5.nes is below

Code: Select all

  .inesprg 1   ; 1x 16KB PRG code
  .ineschr 1   ; 1x  8KB CHR data
  .inesmap 0   ; mapper 0 = NROM, no bank swapping
  .inesmir 1   ; VERT mirroring for HORIZ scrolling
  

;;;;;;;;;;;;;;;

;; DECLARE SOME VARIABLES HERE
  .rsset $0000  ;;start variables at ram location 0
  
scroll     .rs 1  ; horizontal scroll count
nametable  .rs 1  ; which nametable to use, 0 or 1
columnLow  .rs 1  ; low byte of new column address
columnHigh .rs 1  ; high byte of new column address
sourceLow  .rs 1  ; source for column data
sourceHigh .rs 1
columnNumber .rs 1  ; which column of level data to draw
 
;;;;;;;;;;;;
    
  .bank 0
  .org $C000 
RESET:
  SEI          ; disable IRQs
  CLD          ; disable decimal mode
  LDX #$40
  STX $4017    ; disable APU frame IRQ
  LDX #$FF
  TXS          ; Set up stack
  INX          ; now X = 0
  STX $2000    ; disable NMI
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1

clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x
  STA $0300, x
  STA $0400, x
  STA $0500, x
  STA $0600, x
  STA $0700, x
  LDA #$FE
  STA $0200, x
  INX
  BNE clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2


LoadPalettes:
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA #$3F
  STA $2006             ; write the high byte of $3F00 address
  LDA #$00
  STA $2006             ; write the low byte of $3F00 address
  LDX #$00              ; start out at 0
LoadPalettesLoop:
  LDA palette, x        ; load data from address (palette + the value in x)
  STA $2007             ; write to PPU
  INX                   ; X = X + 1
  CPX #$20              ; Compare X to hex $10, decimal 16 - copying 16 bytes = 4 sprites
  BNE LoadPalettesLoop  ; Branch to LoadPalettesLoop if compare was Not Equal to zero
                        ; if compare was equal to 32, keep going down



LoadSprites:
  LDX #$00              ; start at 0
LoadSpritesLoop:
  LDA sprites, x        ; load data from address (sprites +  x)
  STA $0200, x          ; store into RAM address ($0200 + x)
  INX                   ; X = X + 1
  CPX #$10              ; Compare X to hex $20, decimal 16
  BNE LoadSpritesLoop   ; Branch to LoadSpritesLoop if compare was Not Equal to zero
                        ; if compare was equal to 16, keep going down
              
              
InitializeNametables:
  LDA #$01
  STA nametable
  LDA #$00
  STA scroll
  STA columnNumber
InitializeNametablesLoop:
  JSR DrawNewColumn     ; draw bg column
  LDA scroll            ; go to next column
  CLC
  ADC #$08
  STA scroll
  INC columnNumber
  LDA columnNumber      ; repeat for first nametable 
  CMP #$20
  BNE InitializeNametablesLoop
  
  LDA #$00
  STA nametable
  LDA #$00
  STA scroll
  JSR DrawNewColumn     ; draw first column of second nametable
  INC columnNumber
  
  LDA #$00              ; set back to increment +1 mode
  STA $2000
InitializeNametablesDone:
  

InitializeAttributes:
  LDA #$01
  STA nametable
  LDA #$00
  STA scroll
  STA columnNumber
InitializeAttributesLoop:
  JSR DrawNewAttributes     ; draw attribs
  LDA scroll                ; go to next column
  CLC
  ADC #$20
  STA scroll

  LDA columnNumber      ; repeat for first nametable 
  CLC 
  ADC #$04
  STA columnNumber
  CMP #$20
  BNE InitializeAttributesLoop
  
  LDA #$00
  STA nametable
  LDA #$00
  STA scroll
  JSR DrawNewAttributes     ; draw first column of second nametable
InitializeAttributesDone:

  LDA #$21
  STA columnNumber

              
  LDA #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
  STA $2000

  LDA #%00011110   ; enable sprites, enable background, no clipping on left side
  STA $2001

Forever:
  JMP Forever     ;jump back to Forever, infinite loop
  
 

NMI:
  INC scroll            ; add one to our scroll variable each frame


NTSwapCheck:
  LDA scroll            ; check if the scroll just wrapped from 255 to 0
  BNE NTSwapCheckDone  
NTSwap:
  LDA nametable         ; load current nametable number (0 or 1)
  EOR #$01              ; exclusive OR of bit 0 will flip that bit
  STA nametable         ; so if nametable was 0, now 1
                        ;    if nametable was 1, now 0
NTSwapCheckDone:


NewAttribCheck:
  LDA scroll
  AND #%00011111            ; check for multiple of 32
  BNE NewAttribCheckDone    ; if low 5 bits = 0, time to write new attribute bytes
  jsr DrawNewAttributes
NewAttribCheckDone:


NewColumnCheck:
  LDA scroll
  AND #%00000111            ; throw away higher bits to check for multiple of 8
  BNE NewColumnCheckDone    ; done if lower bits != 0
  JSR DrawNewColumn         ; if lower bits = 0, time for new column
  
  lda columnNumber
  clc
  adc #$01             ; go to next column
  and #%01111111       ; only 128 columns of data, throw away top bit to wrap
  sta columnNumber
NewColumnCheckDone:


  LDA #$00
  STA $2003       
  LDA #$02
  STA $4014       ; sprite DMA from $0200
  
  ; run other game graphics updating code here

  LDA #$00
  STA $2006        ; clean up PPU address registers
  STA $2006
  
  LDA scroll
  STA $2005        ; write the horizontal scroll count register

  LDA #$00         ; no vertical scrolling
  STA $2005
    
  ;;This is the PPU clean up section, so rendering the next frame starts properly.
  LDA #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
  ORA nametable    ; select correct nametable for bit 0
  STA $2000
  
  LDA #%00011110   ; enable sprites, enable background, no clipping on left side
  STA $2001
    
  ; run normal game engine code here
  ; reading from controllers, etc
  
  RTI              ; return from interrupt
 
 
 
 

DrawNewColumn:
  LDA scroll       ; calculate new column address using scroll register
  LSR A
  LSR A
  LSR A            ; shift right 3 times = divide by 8
  STA columnLow    ; $00 to $1F, screen is 32 tiles wide

  LDA nametable     ; calculate new column address using current nametable
  EOR #$01          ; invert low bit, A = $00 or $01
  ASL A             ; shift up, A = $00 or $02
  ASL A             ; $00 or $04
  CLC
  ADC #$20          ; add high byte of nametable base address ($2000)
  STA columnHigh    ; now address = $20 or $24 for nametable 0 or 1

  LDA columnNumber  ; column number * 32 = column data offset
  ASL A
  ASL A
  ASL A
  ASL A
  ASL A             
  STA sourceLow
  LDA columnNumber
  LSR A
  LSR A
  LSR A
  STA sourceHigh
  
  LDA sourceLow       ; column data start + offset = address to load column data from
  CLC 
  ADC #LOW(columnData) ; ADC #LOW(columnData)
  STA sourceLow
  LDA sourceHigh
  ADC #HIGH(columnData) ;  ADC #HIGH(columnData)
  STA sourceHigh

DrawColumn:
  LDA #%00000100        ; set to increment +32 mode
  STA $2000
  
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA columnHigh ; LDA columnHigh
  STA $2006             ; write the high byte of column address
  LDA columnLow   ; LDA columnLow
  STA $2006             ; write the low byte of column address
  LDX #$1E              ; copy 30 bytes
  LDY #$00  ;   LDY #$00
DrawColumnLoop:
  LDA [sourceLow], y
  STA $2007
  INY
  DEX
  BNE DrawColumnLoop

  RTS
  
  
  
DrawNewAttributes:
  LDA nametable
  EOR #$01          ; invert low bit, A = $00 or $01
  ASL A             ; shift up, A = $00 or $02
  ASL A             ; $00 or $04
  CLC
  ADC #$23          ; add high byte of attribute base address ($23C0)
  STA columnHigh    ; now address = $23 or $27 for nametable 0 or 1
  
  LDA scroll
  LSR A
  LSR A
  LSR A
  LSR A
  LSR A
  CLC
  ADC #$C0
  STA columnLow     ; attribute base + scroll / 32

  LDA columnNumber  ; (column number / 4) * 8 = column data offset
  AND #%11111100
  ASL A
  STA sourceLow
  LDA columnNumber
  LSR A
  LSR A
  LSR A
  LSR A
  LSR A
  LSR A
  LSR A
  STA sourceHigh
  
  LDA sourceLow       ; column data start + offset = address to load column data from
  CLC 
  ADC #LOW(attribData)
  STA sourceLow
  LDA sourceHigh
  ADC #HIGH(attribData)
  STA sourceHigh

  LDY #$00
  LDA $2002             ; read PPU status to reset the high/low latch
DrawNewAttributesLoop
  LDA columnHigh
  STA $2006             ; write the high byte of column address
  LDA columnLow
  STA $2006             ; write the low byte of column address
  LDA [sourceLow], y    ; copy new attribute byte
  STA $2007
  
  INY
  CPY #$08              ; copy 8 attribute bytes
  BEQ DrawNewAttributesLoopDone 
  
  LDA columnLow         ; next attribute byte is at address + 8
  CLC
  ADC #$08
  STA columnLow
  JMP DrawNewAttributesLoop
DrawNewAttributesLoopDone:

  rts
;;;;;;;;;;;;;;  
  
  
  

  
  .bank 1
  .org $E000
palette:
  .db $22,$29,$1A,$0F,  $22,$36,$17,$0F,  $22,$30,$21,$0F,  $22,$27,$17,$0F   ;;background palette
  .db $22,$16,$27,$18,  $22,$1A,$30,$27,  $22,$16,$30,$27,  $22,$0F,$36,$17   ;;sprite palette

sprites:
     ;vert tile attr horiz
  .db $80, $32, $00, $80   ;sprite 0
  .db $80, $33, $00, $88   ;sprite 1
  .db $88, $34, $00, $80   ;sprite 2
  .db $88, $35, $00, $88   ;sprite 3


columnData:
  .incbin "SMBlevel.bin"

attribData:
  .incbin "SMBattrib.bin"

  .org $FFFA     ;first of the three vectors starts here
  .dw NMI        ;when an NMI happens (once per frame if enabled) the 
                   ;processor will jump to the label NMI:
  .dw RESET      ;when the processor first turns on or is reset, it will jump
                   ;to the label RESET:
  .dw 0          ;external interrupt IRQ is not used in this tutorial
  
  
;;;;;;;;;;;;;;  
  
  
  .bank 2
  .org $0000
  .incbin "mario.chr"   ;includes 8KB graphics file from SMB1

Fiskbit
Posts: 259
Joined: Sat Nov 18, 2017 9:15 pm

Re: Why is My Custom Nametable Rotated 90 Degrees Clockwise?

Post by Fiskbit » Sun Feb 07, 2021 12:07 am

It looks like your code writes to the nametable in columns, finding a starting address by taking column number * 32 and then writing 30 consecutive bytes from there to $2007 in increment-by-32 mode. This means the first 30 bytes of each 32 byte chunk define a vertical strip of nametable data. However, you have the data in horizontal strips, so the first 30 bytes of each 32-byte row are being written top to bottom instead of left to right.

If you store your data in rows like this, your column starting address should just be the column index, and you need to increment the source address by 32 instead of 1 for each byte you transfer to the PPU. Or, you can store your data in columns, though note that because the screen is only 30 tiles tall, your current address calculation (*32) will waste 2 bytes at the end of each column.

justin-rwx
Posts: 12
Joined: Sun Jan 10, 2021 1:12 pm

Re: Why is My Custom Nametable Rotated 90 Degrees Clockwise?

Post by justin-rwx » Sun Feb 07, 2021 3:22 pm

If you store your data in rows like this, your column starting address should just be the column index, and you need to increment the source address by 32 instead of 1 for each byte you transfer to the PPU.
Working with Tile Studio, it seems that it only stores the raw data in rows (or 1 long array, rather). I think I understand what you mean - I will try to reiterate:
  • The data in testMap4.bin is one long row
  • The code is using 32 incriment mode to store data into $2007 as a column (the column loads at the edge of the screen as the scroll counter increments)
  • If I want to load the background as shown in Tile Studio, I would need to ensure that the source file data address is incremented by 32 when being loaded into $2007
I've modified the code as shown below. My intent here was that the data loaded into $2007 would be in the following order...

0 32 64 ... 1 33 65 ... 2 34 66 ... etc...

This way when the data is added to the nametable, it will be in the following order...

Code: Select all

0      1      2   ...  31   
32     33   34  ...  63
64     65   66  ...  95
.      .      .            .
.      .      .            .
.      .      .            .

Code: Select all

SCREEN_X = $30 ; screen x width

DrawColumn:
  LDA #%00000100        ; set to increment +32 mode
  STA $2000
  
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA columnHigh ; LDA columnHigh
  STA $2006             ; write the high byte of column address
  LDA columnLow   ; LDA columnLow
  STA $2006             ; write the low byte of column address
  LDY #$00  ;   LDY #$00
.loop1
  LDX #$1F
  LDA [sourceLow], y
  STA $2007
.loop2 ; this loop adds 31 to y
  tya
  clc
  adc #$20
  tay
  lda [sourceLow], y
  sta $2007
  dex
  bne .loop2
  iny
  cpy SCREEN_X
  bne .loop1
  rts
When I run the program using the modified code above, the result isn't what I was expecting - the game begins flashing random colors in random patterns. Not even the Mario sprite is visible anymore.

Any flaws in my logic, or maybe I missed something in the code?

Image

The full code below

Code: Select all

 .inesprg 1   ; 1x 16KB PRG code
  .ineschr 1   ; 1x  8KB CHR data
  .inesmap 0   ; mapper 0 = NROM, no bank swapping
  .inesmir 1   ; VERT mirroring for HORIZ scrolling
  

;;;;;;;;;;;;;;;

;; DECLARE SOME VARIABLES HERE
  .rsset $0000  ;;start variables at ram location 0
  
scroll     .rs 1  ; horizontal scroll count
nametable  .rs 1  ; which nametable to use, 0 or 1
columnLow  .rs 1  ; low byte of new column address
columnHigh .rs 1  ; high byte of new column address
sourceLow  .rs 1  ; source for column data
sourceHigh .rs 1
columnNumber .rs 1  ; which column of level data to draw

SCREEN_X = $30 ; screen x width
;SCREEN_Y = $32 ; screen y width

;;;;;;;;;;;;
    
  .bank 0
  .org $C000 
RESET:
  SEI          ; disable IRQs
  CLD          ; disable decimal mode
  LDX #$40
  STX $4017    ; disable APU frame IRQ
  LDX #$FF
  TXS          ; Set up stack
  INX          ; now X = 0
  STX $2000    ; disable NMI
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1

clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x
  STA $0300, x
  STA $0400, x
  STA $0500, x
  STA $0600, x
  STA $0700, x
  LDA #$FE
  STA $0200, x
  INX
  BNE clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2


LoadPalettes:
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA #$3F
  STA $2006             ; write the high byte of $3F00 address
  LDA #$00
  STA $2006             ; write the low byte of $3F00 address
  LDX #$00              ; start out at 0
LoadPalettesLoop:
  LDA palette, x        ; load data from address (palette + the value in x)
  STA $2007             ; write to PPU
  INX                   ; X = X + 1
  CPX #$20              ; Compare X to hex $10, decimal 16 - copying 16 bytes = 4 sprites
  BNE LoadPalettesLoop  ; Branch to LoadPalettesLoop if compare was Not Equal to zero
                        ; if compare was equal to 32, keep going down



LoadSprites:
  LDX #$00              ; start at 0
LoadSpritesLoop:
  LDA sprites, x        ; load data from address (sprites +  x)
  STA $0200, x          ; store into RAM address ($0200 + x)
  INX                   ; X = X + 1
  CPX #$10              ; Compare X to hex $20, decimal 16
  BNE LoadSpritesLoop   ; Branch to LoadSpritesLoop if compare was Not Equal to zero
                        ; if compare was equal to 16, keep going down
              
              
InitializeNametables:
  LDA #$01
  STA nametable
  LDA #$00
  STA scroll
  STA columnNumber
InitializeNametablesLoop:
  JSR DrawNewColumn     ; draw bg column
  LDA scroll            ; go to next column
  CLC
  ADC #$08
  STA scroll
  INC columnNumber
  LDA columnNumber      ; repeat for first nametable 
  CMP #$20
  BNE InitializeNametablesLoop
  
  LDA #$00
  STA nametable
  LDA #$00
  STA scroll
  JSR DrawNewColumn     ; draw first column of second nametable
  INC columnNumber
  
  LDA #$00              ; set back to increment +1 mode
  STA $2000
InitializeNametablesDone:
  

InitializeAttributes:
  LDA #$01
  STA nametable
  LDA #$00
  STA scroll
  STA columnNumber
InitializeAttributesLoop:
  JSR DrawNewAttributes     ; draw attribs
  LDA scroll                ; go to next column
  CLC
  ADC #$20
  STA scroll

  LDA columnNumber      ; repeat for first nametable 
  CLC 
  ADC #$04
  STA columnNumber
  CMP #$20
  BNE InitializeAttributesLoop
  
  LDA #$00
  STA nametable
  LDA #$00
  STA scroll
  JSR DrawNewAttributes     ; draw first column of second nametable
InitializeAttributesDone:

  LDA #$21
  STA columnNumber

              
  LDA #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
  STA $2000

  LDA #%00011110   ; enable sprites, enable background, no clipping on left side
  STA $2001

Forever:
  JMP Forever     ;jump back to Forever, infinite loop
  
 

NMI:
  INC scroll            ; add one to our scroll variable each frame


NTSwapCheck:
  LDA scroll            ; check if the scroll just wrapped from 255 to 0
  BNE NTSwapCheckDone  
NTSwap:
  LDA nametable         ; load current nametable number (0 or 1)
  EOR #$01              ; exclusive OR of bit 0 will flip that bit
  STA nametable         ; so if nametable was 0, now 1
                        ;    if nametable was 1, now 0
NTSwapCheckDone:


NewAttribCheck:
  LDA scroll
  AND #%00011111            ; check for multiple of 32
  BNE NewAttribCheckDone    ; if low 5 bits = 0, time to write new attribute bytes
  jsr DrawNewAttributes
NewAttribCheckDone:


NewColumnCheck:
  LDA scroll
  AND #%00000111            ; throw away higher bits to check for multiple of 8
  BNE NewColumnCheckDone    ; done if lower bits != 0
  JSR DrawNewColumn         ; if lower bits = 0, time for new column
  
  lda columnNumber
  clc
  adc #$01             ; go to next column
  and #%01111111       ; only 128 columns of data, throw away top bit to wrap
  sta columnNumber
NewColumnCheckDone:


  LDA #$00
  STA $2003       
  LDA #$02
  STA $4014       ; sprite DMA from $0200
  
  ; run other game graphics updating code here

  LDA #$00
  STA $2006        ; clean up PPU address registers
  STA $2006
  
  LDA scroll
  STA $2005        ; write the horizontal scroll count register

  LDA #$00         ; no vertical scrolling
  STA $2005
    
  ;;This is the PPU clean up section, so rendering the next frame starts properly.
  LDA #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
  ORA nametable    ; select correct nametable for bit 0
  STA $2000
  
  LDA #%00011110   ; enable sprites, enable background, no clipping on left side
  STA $2001
    
  ; run normal game engine code here
  ; reading from controllers, etc
  
  RTI              ; return from interrupt
 
 
 
 

DrawNewColumn:
  LDA scroll       ; calculate new column address using scroll register
  LSR A
  LSR A
  LSR A            ; shift right 3 times = divide by 8
  STA columnLow    ; $00 to $1F, screen is 32 tiles wide

  LDA nametable     ; calculate new column address using current nametable
  EOR #$01          ; invert low bit, A = $00 or $01
  ASL A             ; shift up, A = $00 or $02
  ASL A             ; $00 or $04
  CLC
  ADC #$20          ; add high byte of nametable base address ($2000)
  STA columnHigh    ; now address = $20 or $24 for nametable 0 or 1

  LDA columnNumber  ; column number * 32 = column data offset
  ASL A
  ASL A
  ASL A
  ASL A
  ASL A             
  STA sourceLow
  LDA columnNumber
  LSR A
  LSR A
  LSR A
  STA sourceHigh
  
  LDA sourceLow       ; column data start + offset = address to load column data from
  CLC 
  ADC #LOW(columnData) ; ADC #LOW(columnData)
  STA sourceLow
  LDA sourceHigh
  ADC #HIGH(columnData) ;  ADC #HIGH(columnData)
  STA sourceHigh

DrawColumn:
  LDA #%00000100        ; set to increment +32 mode
  STA $2000
  
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA columnHigh ; LDA columnHigh
  STA $2006             ; write the high byte of column address
  LDA columnLow   ; LDA columnLow
  STA $2006             ; write the low byte of column address
  LDY #$00  ;   LDY #$00
.loop1
  LDX #$1F
  LDA [sourceLow], y
  STA $2007
.loop2 ; this loop adds 31 to y
  tya
  clc
  adc #$20
  tay
  lda [sourceLow], y
  sta $2007
  dex
  bne .loop2
  iny
  cpy SCREEN_X
  bne .loop1
  rts
  
  
  
DrawNewAttributes:
  LDA nametable
  EOR #$01          ; invert low bit, A = $00 or $01
  ASL A             ; shift up, A = $00 or $02
  ASL A             ; $00 or $04
  CLC
  ADC #$23          ; add high byte of attribute base address ($23C0)
  STA columnHigh    ; now address = $23 or $27 for nametable 0 or 1
  
  LDA scroll
  LSR A
  LSR A
  LSR A
  LSR A
  LSR A
  CLC
  ADC #$C0
  STA columnLow     ; attribute base + scroll / 32

  LDA columnNumber  ; (column number / 4) * 8 = column data offset
  AND #%11111100
  ASL A
  STA sourceLow
  LDA columnNumber
  LSR A
  LSR A
  LSR A
  LSR A
  LSR A
  LSR A
  LSR A
  STA sourceHigh
  
  LDA sourceLow       ; column data start + offset = address to load column data from
  CLC 
  ADC #LOW(attribData)
  STA sourceLow
  LDA sourceHigh
  ADC #HIGH(attribData)
  STA sourceHigh

  LDY #$00
  LDA $2002             ; read PPU status to reset the high/low latch
DrawNewAttributesLoop
  LDA columnHigh
  STA $2006             ; write the high byte of column address
  LDA columnLow
  STA $2006             ; write the low byte of column address
  LDA [sourceLow], y    ; copy new attribute byte
  STA $2007
  
  INY
  CPY #$08              ; copy 8 attribute bytes
  BEQ DrawNewAttributesLoopDone 
  
  LDA columnLow         ; next attribute byte is at address + 8
  CLC
  ADC #$08
  STA columnLow
  JMP DrawNewAttributesLoop
DrawNewAttributesLoopDone:

  rts
;;;;;;;;;;;;;;  
  
  
  

  
  .bank 1
  .org $E000
palette:
  .db $22,$29,$1A,$0F,  $22,$36,$17,$0F,  $22,$30,$21,$0F,  $22,$27,$17,$0F   ;;background palette
  .db $22,$16,$27,$18,  $22,$1A,$30,$27,  $22,$16,$30,$27,  $22,$0F,$36,$17   ;;sprite palette

sprites:
     ;vert tile attr horiz
  .db $80, $32, $00, $80   ;sprite 0
  .db $80, $33, $00, $88   ;sprite 1
  .db $88, $34, $00, $80   ;sprite 2
  .db $88, $35, $00, $88   ;sprite 3


columnData:
  .incbin "testMap4.bin"

attribData:
  .incbin "SMBattrib.bin"

  .org $FFFA     ;first of the three vectors starts here
  .dw NMI        ;when an NMI happens (once per frame if enabled) the 
                   ;processor will jump to the label NMI:
  .dw RESET      ;when the processor first turns on or is reset, it will jump
                   ;to the label RESET:
  .dw 0          ;external interrupt IRQ is not used in this tutorial
  
  
;;;;;;;;;;;;;;  
  
  
  .bank 2
  .org $0000
  .incbin "mario.chr"   ;includes 8KB graphics file from SMB1

User avatar
Controllerhead
Posts: 218
Joined: Tue Nov 13, 2018 4:58 am
Location: $4016
Contact:

Re: Why is My Custom Nametable Rotated 90 Degrees Clockwise?

Post by Controllerhead » Sun Feb 07, 2021 3:48 pm

justin-rwx wrote:
Sun Feb 07, 2021 3:22 pm
The full code below
mmmm not quite

Code: Select all

columnData:
  .incbin "testMap4.bin"

attribData:
  .incbin "SMBattrib.bin"
Image

justin-rwx
Posts: 12
Joined: Sun Jan 10, 2021 1:12 pm

Re: Why is My Custom Nametable Rotated 90 Degrees Clockwise?

Post by justin-rwx » Sun Feb 07, 2021 4:10 pm

Controllerhead wrote:
Sun Feb 07, 2021 3:48 pm
justin-rwx wrote:
Sun Feb 07, 2021 3:22 pm
The full code below
mmmm not quite

Code: Select all

columnData:
  .incbin "testMap4.bin"

attribData:
  .incbin "SMBattrib.bin"
My mistake - just to clarify, my background with the '3' is in the testMap4.bin file, whereas the original background as given in the Nerdy-Nights example uses SMlevel.bin. I am trying to make my own background and have it show up in this tutorial example.

Fiskbit
Posts: 259
Joined: Sat Nov 18, 2017 9:15 pm

Re: Why is My Custom Nametable Rotated 90 Degrees Clockwise?

Post by Fiskbit » Sun Feb 07, 2021 4:21 pm

The data in testMap4.bin is one long row
Yes, sort of. It's stored in an increment-by-1 fashion, so bytes 0-31 are tiles 0-31 of row 0, bytes 32-63 are tiles 0-31 of row 1, etc. That is, it's stored left to right, top to bottom, with the start of row n+1 immediately following the end of row n.
The code is using 32 incriment mode to store data into $2007 as a column (the column loads at the edge of the screen as the scroll counter increments
Yes. Because rows are 32 tiles wide, increment-by-32 will go vertically, updating tile m of row n, then tile m of row n+1, etc.
If I want to load the background as shown in Tile Studio, I would need to ensure that the source file data address is incremented by 32 when being loaded into $2007
Yes, this is correct.
I've modified the code as shown below. My intent here was that the data loaded into $2007 would be in the following order...

0 32 64 ... 1 33 65 ... 2 34 66 ... etc...

This way when the data is added to the nametable, it will be in the following order...

Code: Select all

0      1      2   ...  31   
32     33   34  ...  63
64     65   66  ...  95
.      .      .            .
.      .      .            .
.      .      .            .
This is reasonable, but I want to distinguish a bit between two cases. When you're scrolling horizontally, you want to bring the data in in columns because horizontal scrolling brings new columns in from the edge of the screen. If you're building the whole screen at once, it doesn't matter which direction you go. You could implement it as 32 column loads using your scrolling code, or with separate code that more efficiently does it all at once, which in this case would use increment-by-1.

The cases also have different requirements in that the scrolling case has to be able to fit the updates into vblank, a 20-scanline-long period between the bottom of the screen and the top where VRAM is accessible, while drawing a whole screen is likely done with rendering disabled, so you're free to do the writes whenever you want.
SCREEN_X = $30 ; screen x width
This should be $20 (or 32 decimal).

Code: Select all

DrawColumn:
  LDA #%00000100        ; set to increment +32 mode
  STA $2000
  
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA columnHigh ; LDA columnHigh
  STA $2006             ; write the high byte of column address
  LDA columnLow   ; LDA columnLow
  STA $2006             ; write the low byte of column address
  LDY #$00  ;   LDY #$00
.loop1
  LDX #$1F
  LDA [sourceLow], y
  STA $2007
.loop2 ; this loop adds 31 to y
  tya
  clc
  adc #$20
  tay
  lda [sourceLow], y
  sta $2007
  dex
  bne .loop2
  iny
  cpy SCREEN_X
  bne .loop1
  rts
Is this function for drawing individual columns or drawing the whole screen? The name suggests it's just drawing a single column, but the code tries to do the whole screen. You need to have a single-column-drawing mechanism to deal with the scrolling case, and can implement the whole-screen-drawing case either as its own function (which can go in rows, since your data is stored that way) or by calling the single-column-drawing function 32 times. The latter is simplest right now and allows you to remove the second loop from this function and have it in the outer function.

This function looks like it will never exit. You've overloaded Y so that it's both an index for your source address (lda [sourceLow],y) and the current column (cpy SCREEN_X), which goes away if you make this a function just handle one column at a time. The way you're managing Y for sourceLow is correct, but when it overflows (C == 1), you need to increment sourceHigh. The nametable is $3C0 bytes and Y can only be $00-FF, so it will wrap around. Without incrementing sourceHigh, you'll keep loading from the first $100 bytes of the table.

LDX #$1F should also be LDX #$1E; the former will load 31 bytes, but you want 30 because columns are 30 tiles high.

User avatar
Controllerhead
Posts: 218
Joined: Tue Nov 13, 2018 4:58 am
Location: $4016
Contact:

Re: Why is My Custom Nametable Rotated 90 Degrees Clockwise?

Post by Controllerhead » Sun Feb 07, 2021 4:36 pm

Fiskbit wrote:
Sun Feb 07, 2021 4:21 pm
You've overloaded Y so that it's both an index for your source address (lda [sourceLow],y) and the current column (cpy SCREEN_X)
This. Your DrawColumn loop is spinning into oblivion. I would use a temporary memory address as a second counter instead.
Image

justin-rwx
Posts: 12
Joined: Sun Jan 10, 2021 1:12 pm

Re: Why is My Custom Nametable Rotated 90 Degrees Clockwise?

Post by justin-rwx » Sun Feb 14, 2021 10:21 pm

So after about a week of staring at the code and trying everything under the sun, I believe I got the result I am expecting - I am finally now able to create a map in Tile Studio and set the map as a scrolling background using the code below. There are two key changes I made to the tutorial code that I explain further.

Image

My first change was to set the columnNumber as the sourceLow since the top column tile on the screen should correlate to the same tile as in the columnData. Next, I set the high bit of columnData directly to sourceHigh - I'm not completely sure if this is correct, but things seem to work so far. After, I needed to grab the next vertical piece of data from the columnData and add it to $2007, so I add the total width of tiles in the level to the y required for the indirect indexed addressing, incrementing the sourceHigh if required. My level is 64 tiles wide, therefore, I add $40 to the y value.

Code: Select all

DrawNewColumn:
  LDA scroll       ; calculate new column address using scroll register
  LSR A
  LSR A
  LSR A            ; shift right 3 times = divide by 8
  STA columnLow    ; $00 to $1F, screen is 32 tiles wide

  LDA nametable     ; calculate new column address using current nametable
  EOR #$01          ; invert low bit, A = $00 or $01
  ASL A             ; shift up, A = $00 or $02
  ASL A             ; $00 or $04
  CLC
  ADC #$20          ; add high byte of nametable base address ($2000)
  STA columnHigh    ; now address = $20 or $24 for nametable 0 or 1
  
  ; LDA columnNumber  ; column number * 32 = column data offset
  ; ASL A
  ; ASL A
  ; ASL A
  ; ASL A
  ; ASL A             
  ; STA sourceLow
  ; LDA columnNumber
  ; AND #%11111000
  ; LSR A
  ; LSR A
  ; LSR A
  ; STA sourceHigh
  
  ; LDA sourceLow       ; column data start + offset = address to load column data from
  ; CLC 
  ; ADC #LOW(columnData)
  ; STA sourceLow
  ; LDA sourceHigh
  ; ADC #HIGH(columnData)
  ; STA sourceHigh
  
  LDA columnNumber       ; column data start + offset = address to load column data from
  CLC 
  ADC #LOW(columnData)
  STA sourceLow
  
  lda #HIGH(columnData)
  STA sourceHigh

DrawColumn:
  LDA #%00000100        ; set to increment +32 mode
  STA $2000
  
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA columnHigh
  STA $2006             ; write the high byte of column address
  LDA columnLow
  STA $2006             ; write the low byte of column address
  LDX #$1E            ; copy 30 bytes
  LDY #$00
DrawColumnLoop:
  LDA [sourceLow], y
  STA $2007
  tya
  clc 
  adc #$40 ; MUST EQUAL COLUMN LEVEL LENGTH The width of the level to get to the next vertical tile
  tay
  lda sourceHigh
  adc #$00
  sta sourceHigh
  DEX
  BNE DrawColumnLoop
The next change required was that in NewColumnCheck, the columnNumber must be wrapped after the level length is surpassed. In my current example, my map was 64 tiles wide, therefore I needed to restart column Numbering once I exceed 63 tiles.

Code: Select all

NewColumnCheck:
  LDA scroll
  AND #%00000111           ; throw away higher bits
  BNE NewColumnCheckDone   ; done if lower bits != 0
  JSR DrawNewColumn        ; if lower bits = 0, time for new column
  
  lda columnNumber
  clc
  adc #$01             ; go to next column
  ;and #%01111111       ; only 128 columns of data, throw away top bit to wrap
  and #%00111111  ; MUST EQUAL LEVEL COLUMN LENGTH column number must wrap after total column in level have been passed
  sta columnNumber
NewColumnCheckDone:
Do the changes above provide a robust enough 'template' for a SMB-style game? My thought is that I could just adjust the and #%00111111 size in the NewColumnCheckDone function as well as the adc #$40 in the DrawColumnLoop for a new map size. My concern is that there is some map size limit I will hit, or some other unforeseen limit with this method of drawing columns.
  • What is the maximum length in tiles that a level can be? Assuming a SMB-style leve that is only 30 tiles in height
  • If there is a maximum length, how can I have a level that is longer? Could I break the level into parts, each with their own .bin file?
Full code below

Code: Select all

 .inesprg 1   ; 1x 16KB PRG code
  .ineschr 1   ; 1x  8KB CHR data
  .inesmap 0   ; mapper 0 = NROM, no bank swapping
  .inesmir 1   ; VERT mirroring for HORIZ scrolling
  

;;;;;;;;;;;;;;;

;; DECLARE SOME VARIABLES HERE
  .rsset $0000  ;;start variables at ram location 0
  
scroll     .rs 1  ; horizontal scroll count
nametable  .rs 1  ; which nametable to use, 0 or 1
columnLow  .rs 1  ; low byte of new column address
columnHigh .rs 1  ; high byte of new column address
sourceLow  .rs 1  ; source for column data
sourceHigh .rs 1
columnNumber .rs 1  ; which column of level data to draw
 
;;;;;;;;;;;;
    
  .bank 0
  .org $C000 
RESET:
  SEI          ; disable IRQs
  CLD          ; disable decimal mode
  LDX #$40
  STX $4017    ; disable APU frame IRQ
  LDX #$FF
  TXS          ; Set up stack
  INX          ; now X = 0
  STX $2000    ; disable NMI
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1

clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x
  STA $0300, x
  STA $0400, x
  STA $0500, x
  STA $0600, x
  STA $0700, x
  LDA #$FE
  STA $0200, x
  INX
  BNE clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2


LoadPalettes:
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA #$3F
  STA $2006             ; write the high byte of $3F00 address
  LDA #$00
  STA $2006             ; write the low byte of $3F00 address
  LDX #$00              ; start out at 0
LoadPalettesLoop:
  LDA palette, x        ; load data from address (palette + the value in x)
  STA $2007             ; write to PPU
  INX                   ; X = X + 1
  CPX #$20              ; Compare X to hex $10, decimal 16 - copying 16 bytes = 4 sprites
  BNE LoadPalettesLoop  ; Branch to LoadPalettesLoop if compare was Not Equal to zero
                        ; if compare was equal to 32, keep going down



LoadSprites:
  LDX #$00              ; start at 0
LoadSpritesLoop:
  LDA sprites, x        ; load data from address (sprites +  x)
  STA $0200, x          ; store into RAM address ($0200 + x)
  INX                   ; X = X + 1
  CPX #$10              ; Compare X to hex $20, decimal 16
  BNE LoadSpritesLoop   ; Branch to LoadSpritesLoop if compare was Not Equal to zero
                        ; if compare was equal to 16, keep going down
              
              
InitializeNametables:
  LDA #$01
  STA nametable
  LDA #$00
  STA scroll
  STA columnNumber
InitializeNametablesLoop:
  JSR DrawNewColumn     ; draw bg column
  LDA scroll            ; go to next column
  CLC
  ADC #$08
  STA scroll
  INC columnNumber
  LDA columnNumber      ; repeat for first nametable 
  CMP #$20
  BNE InitializeNametablesLoop
  
  LDA #$00
  STA nametable
  LDA #$00
  STA scroll
  JSR DrawNewColumn     ; draw first column of second nametable
  INC columnNumber
  
  LDA #$00              ; set back to increment +1 mode
  STA $2000
InitializeNametablesDone:
  


FillAttrib0:
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA #$23
  STA $2006             ; write the high byte of $23C0 address (nametable 0 attributes)
  LDA #$C0
  STA $2006             ; write the low byte of $23C0 address
  LDX #$40              ; fill 64 bytes
  LDA #$00
FillAttrib0Loop:
  STA $2007
  DEX
  BNE FillAttrib0Loop


FillAttrib1:
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA #$27
  STA $2006             ; write the high byte of $27C0 address (nametable 1 attributes)
  LDA #$C0
  STA $2006             ; write the low byte of $27C0 address
  LDX #$40              ; fill 64 bytes
  LDA #$FF
FillAttrib1Loop:
  STA $2007
  DEX
  BNE FillAttrib1Loop

          
              
  LDA #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
  STA $2000

  LDA #%00011110   ; enable sprites, enable background, no clipping on left side
  STA $2001

Forever:
  JMP Forever     ;jump back to Forever, infinite loop
  
 

NMI:
  INC scroll       ; add one to our scroll variable each frame


NTSwapCheck:
  LDA scroll       ; check if the scroll just wrapped from 255 to 0
  BNE NTSwapCheckDone  
NTSwap:
  LDA nametable    ; load current nametable number (0 or 1)
  EOR #$01         ; exclusive OR of bit 0 will flip that bit
  STA nametable    ; so if nametable was 0, now 1
                   ;    if nametable was 1, now 0
NTSwapCheckDone:


NewColumnCheck:
  LDA scroll
  AND #%00000111           ; throw away higher bits
  BNE NewColumnCheckDone   ; done if lower bits != 0
  JSR DrawNewColumn        ; if lower bits = 0, time for new column
  
  lda columnNumber
  clc
  adc #$01             ; go to next column
  ;and #%01111111       ; only 128 columns of data, throw away top bit to wrap
  and #%00111111  ; MUST EQUAL LEVEL COLUM LENGTH column number must wrap after total column in level have been passed
  sta columnNumber
NewColumnCheckDone:


  LDA #$00
  STA $2003       
  LDA #$02
  STA $4014       ; sprite DMA from $0200
  
  ; run other game graphics updating code here

  LDA #$00
  STA $2006        ; clean up PPU address registers
  STA $2006
  
  LDA scroll
  STA $2005        ; write the horizontal scroll count register

  LDA #$00         ; no vertical scrolling
  STA $2005
    
  ;;This is the PPU clean up section, so rendering the next frame starts properly.
  LDA #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
  ORA nametable    ; select correct nametable for bit 0
  STA $2000
  
  LDA #%00011110   ; enable sprites, enable background, no clipping on left side
  STA $2001
    
  ; run normal game engine code here
  ; reading from controllers, etc
  
  RTI              ; return from interrupt

DrawNewColumn:
  LDA scroll       ; calculate new column address using scroll register
  LSR A
  LSR A
  LSR A            ; shift right 3 times = divide by 8
  STA columnLow    ; $00 to $1F, screen is 32 tiles wide

  LDA nametable     ; calculate new column address using current nametable
  EOR #$01          ; invert low bit, A = $00 or $01
  ASL A             ; shift up, A = $00 or $02
  ASL A             ; $00 or $04
  CLC
  ADC #$20          ; add high byte of nametable base address ($2000)
  STA columnHigh    ; now address = $20 or $24 for nametable 0 or 1
  
  LDA columnNumber       ; column data start + offset = address to load column data from
  CLC 
  ADC #LOW(columnData)
  STA sourceLow
  
  lda #HIGH(columnData)
  STA sourceHigh

DrawColumn:
  LDA #%00000100        ; set to increment +32 mode
  STA $2000
  
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA columnHigh
  STA $2006             ; write the high byte of column address
  LDA columnLow
  STA $2006             ; write the low byte of column address
  LDX #$1E            ; copy 30 bytes
  LDY #$00
DrawColumnLoop:
  LDA [sourceLow], y
  STA $2007
  tya
  clc 
  adc #$40 ; MUST EQUAL COLUMN LEVEL LENGTH The width of the level to get to the next vertical tile
  tay
  lda sourceHigh
  adc #$00
  sta sourceHigh
  DEX
  BNE DrawColumnLoop

  RTS
;;;;;;;;;;;;;;  
  
  .bank 1
  .org $E000
palette:
  .db $22,$29,$1A,$0F,  $22,$36,$17,$0F,  $22,$30,$21,$0F,  $22,$27,$17,$0F   ;;background palette
  .db $22,$1C,$15,$14,  $22,$02,$38,$3C,  $22,$1C,$15,$14,  $22,$02,$38,$3C   ;;sprite palette

sprites:
     ;vert tile attr horiz
  .db $80, $32, $00, $80   ;sprite 0
  .db $80, $33, $00, $88   ;sprite 1
  .db $88, $34, $00, $80   ;sprite 2
  .db $88, $35, $00, $88   ;sprite 3


columnData:
  .incbin "testMap12.bin"


  .org $FFFA     ;first of the three vectors starts here
  .dw NMI        ;when an NMI happens (once per frame if enabled) the 
                   ;processor will jump to the label NMI:
  .dw RESET      ;when the processor first turns on or is reset, it will jump
                   ;to the label RESET:
  .dw 0          ;external interrupt IRQ is not used in this tutorial
  
  
;;;;;;;;;;;;;;  
  
  
  .bank 2
  .org $0000
  .incbin "mario.chr"   ;includes 8KB graphics file from SMB1

User avatar
Controllerhead
Posts: 218
Joined: Tue Nov 13, 2018 4:58 am
Location: $4016
Contact:

Re: Why is My Custom Nametable Rotated 90 Degrees Clockwise?

Post by Controllerhead » Mon Feb 15, 2021 12:51 am

justin-rwx wrote:
Sun Feb 14, 2021 10:21 pm
My concern is that there is some map size limit I will hit, or some other unforeseen limit with this method of drawing columns.
You're going to run into 2 problems storing 1x1 tile data: Storage capacity and Attribute data. The background only allows one palette for a 16x16px wide background area. Most games used metatiles of predefined 2x2 tile patterns to match this 16x16px limitation and also save a ton of storage space. That would be my suggestion.
justin-rwx wrote:
Sun Feb 14, 2021 10:21 pm
What is the maximum length in tiles that a level can be
The good news is that there is no limit per-se. You can fill your 32KB of PRG space in an NROM with whatever data you like. If you want more space than that, you will have to implement a mapper with bank-switching functionality. The highest that a game of the era used was 512KB of PRG space, but, some existing mappers can theoretically go higher than that.

PS- It would help in the future to zip up and attach your full source code folder with your included files.
We can get you up and running much easier that way =)
Image

tepples
Posts: 22288
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Why is My Custom Nametable Rotated 90 Degrees Clockwise?

Post by tepples » Mon Feb 15, 2021 6:52 am

justin-rwx wrote:
Sun Feb 14, 2021 10:21 pm
What is the maximum length in tiles that a level can be? Assuming a SMB-style leve that is only 30 tiles in height
With appropriate compression, level length is limited only by how fast the player can react to oncoming terrain and how long the player continues in a session. A continuous runner game like Canabalt generates its maps procedurally, meaning each map is built using rules fed by a random number generator.

Assume for a moment that the player can see oncoming obstacles at 4 pixels per frame and can play for 15 minutes at a time. This means 240 pixels per second, 14400 pixels per minute, or 216,000 pixels (843 screens) in a play session.

Post Reply