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

Loading 16x16 Metatiles

Loading 16x16 Metatiles
by on (#42504)
For the past couple of days I've been trying to come up with a way to load 16x16 metatiles into the PPU. The only way I could imagine is to have a buffer that you load into prior to pulling it all to the screen. As it is right now, I am working on getting the top 256 tiles in the PPU. I've tried a few things, and so far this is getting me the best results, but it's still not displaying properly.

What I am doing is grabbing the level number, which is in multiples of two, and getting the address from a table:

Code:
   jsr PPU_off
   ldy game_level

   lda level_data_start,y
   sta address_temp_lo
   lda level_data_start+1,y
   sta address_temp_hi

; The table of word sized labels
level_data_start:
   .addr board_1_1, board_1_2, board_1_3, board_1_4


After that, I am using a pointer to get the kind of block in the address (in this case, board_1_1).

Code:
 
   ldy #$00
:   lda (address_temp_lo),y
   sta get_block


After that, I do a couple of subroutines, one to grab the tile numbers from, which is also my first time dealing with jump tables, and the other routine sticks the tiles in a buffer located at $600. Then I increment Y, check it to be #$40, and go loading from the buffer at $600 to the PPU:

Code:
   jsr choose_block

   jsr do_block_buffer
   iny
   cpy #$40
   bne :-
   inc address_temp_hi

   jsr do_top_nametable

; ------------------------------------------

choose_block:
   lda get_block
   asl a
   tax
   lda block_types+1,x
   pha
   lda block_types,x
   pha
   rts

; Below is the table of addresses for what to do for each block
block_types:
   .addr open_block-1
   .addr stage1_solid_block-1
   .addr electric_side_block-1
   .addr stage2_solid_block-1
   .addr electric_top_block-1

; Here are the kind of things I do in those subroutines:
open_block:
   lda #$00
   sta tile_1
   sta tile_2
   sta tile_3
   sta tile_4
   rts
stage1_solid_block:
   lda #$01
   sta tile_1
   lda #$02
   sta tile_2
; etc etc....

; Here is where the tiles are put into a buffer
do_block_buffer:
   ldx metatile_index
   lda tile_1
   sta bg_buffer,x
   inx
   lda tile_2
   sta bg_buffer,x
   txa
   clc
   adc #31
   tax
   lda tile_3
   sta bg_buffer,x
   inx
   lda tile_4
   sta bg_buffer,x
   txa
   sec
   sbc #31
   tax
   stx metatile_index
;   bne :+
;   inc metatile_index+1     ; I forgot to take this block out of the code
;:                                        ; edited now
   rts

; Pull it to the PPU

do_top_nametable:
   lda #$20
   sta $2006
   ldx #$00
   sta $2006
:   lda bg_buffer,x
   sta $2007
   inx
   bne :-
   rts


The left pic is what it looks like when I build it, and the right one is what it should look similar to (top 256 tiles):

Image Image

I can't figure out what is going on at all :/ If anyone can spot something I'm doing entirely wrong, let me know. This is getting pretty aggravating.

by on (#42507)
You are overwriting the bottom row of the metatiles because of the way metatile_index is incremented. Think about it: after writing the last metatile of the first row, that "sbc #31" will try to place the pointer to the left of it, but since the row ended that will actually be the first tile of the next row of tiles, so the new row of metatiles overwrites the bottom half of the previous one.

What you have to do is detect the end of a metatile row and add 32 to the index when that happens. Maybe you can fix it with this (between "tax" and "stx metatile_index"):

Code:
   and #%00011111
   bne :+
   clc
   adc #32
   tax
:


EDIT: I just want to add that I think you are overcomplicating things, and wasting a lot of ROM by storing metatiles like that (as code, instead of just data). There are many ways you can make that code faster, smaller and simpler. If you want some tips for that, let us know.

by on (#42511)
Well, I have switched those numbers to numerous things (the two 31's above). Because, I lay two tiles down, add 31 to that to get to the next row, then lay two tiles down. OHHHHHHH! Wait, after the whole first two rows are laid down, then the rest is getting over-written... crap. Okay. Because the first row goes down, then the second row, then the third row doesn't start on the third row, instead it starts on the second row... okay. Wow, how stupid haha Thanks for that : )

About the way I store it, here is how I store the metatiles:

Code:
board_1_1:
   .byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
   .byte $01,$01,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$01,$01
   .byte $01,$02,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$02,$01
   .byte $01,$01,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$01,$01

   .byte $01,$01,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$01,$01
   .byte $01,$02,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$02,$01
   .byte $01,$01,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$01,$01
   .byte $01,$02,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$02,$01

   .byte $01,$01,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$01,$01
   .byte $01,$01,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$01,$01
   .byte $01,$02,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$02,$01
   .byte $01,$01,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$01,$01

; This is how I define each metatile block:
open_block:
   lda #$00
   sta tile_1
   sta tile_2
   sta tile_3
   sta tile_4
   rts
stage1_solid_block:
   lda #$01
   sta tile_1
   lda #$02
   sta tile_2
   lda #$03
   sta tile_3
   lda #$04
   sta tile_4
   rts
electric_side_block:
   lda #$05
   sta tile_1
   sta tile_2
   lda #$06
   sta tile_3
   sta tile_4
   rts
stage2_solid_block:
   lda #$07
   sta tile_1
   lda #$08
   sta tile_2
   lda #$09
   sta tile_3
   lda #$0a
   sta tile_4
   rts
electric_top_block:
   lda #$0b
   sta tile_1
   sta tile_3
   lda #$0c
   sta tile_2
   sta tile_4
   rts



The blocks are called from the jump table, and those are the only metatiles I have defined right now. If there is a better way, I am always open to suggestions!

EDIT: Oh, and the fix didn't fix it, but after rereading your post and what-not, it finally clicked what was going on, so now I can mess with it some more. Thanks man : )

by on (#42512)
Roth wrote:
Oh, and the fix didn't fix it, but after rereading your post and what-not, it finally clicked what was going on, so now I can mess with it some more. Thanks man : )

Oh, there is a glitch in my fix! it should be:
Code:
and #%00011111
   bne :+
   txa
   clc
   adc #32
   tax
:

Forgot the TXA! Maybe it will work now. The 31s you used should work fine.

by on (#42515)
: D

Image

You're a king man. Now I have to check this out and pick it apart. Every time ANDs and ORAs and EORs are involved, I have to look at things and think hard hehe

by on (#42516)
AND: mask bits off
ORA: turning bits on
EOR: bit toggling

by on (#42518)
Even worse it BCC and BCS. I can never remember which one means Branch on Greater than or which means Branch on Less than. I think Tokumaru said once that it's easy to remember because if the carry has been cleared, M > A because the 1 has been borrowed or something, and I loosely remember that.

by on (#42519)
That "and #%00011111" is used to detect the end of a row. A row has 32 tiles, numbered 0 to 31. 31 in binary is 00011111, and if you add 1 to it, it becomes 00100000 (32 decimal). This means that every time a row ends, the lower 5 bits will switch from 11111 to 00000. The higher bits don't matter. When you AND 00011111 with another value, the result will only be 0 if all 5 bits of the value are 0, so the Z flag tells us if the row ended or not.

by on (#42520)
Now for some optimizations to your code! =)

First, it's great that you understood the jump table + RTS trick, but this is hardly a good place to use it. All those metatile routines with lots of LDAs and STAs will cost you a nice chunk of ROM down the road. Instead of a table of routines, you could have 4 tables listing the tiles used by the metatiles:
Code:
            open
            |    stage1_solid
            |    |    electric_side
            |    |    |    (...)
tile1:
        .db $00, $01, $05, (...)
tile2:
        .db $00, $02, $05, (...)
tile3:
        .db $00, $03, $06, (...)
tile4:
        .db $00, $04, $06, (...)

That way, after "lda (address_temp_lo),y" you could read the tiles much faster with:
Code:
   tax
   lda tile1, x
   sta tile_1
   lda tile2, x
   sta tile_2
   lda tile3, x
   sta tile_3
   lda tile4, x
   sta tile_4

OK, but the next optimization is actually to get rid of the tile_1, tile_2, tile_3, tile_4 variables. There is no need for them when you can read the tiles directly from the table. In your do_block_buffer routine, you could just read from the tile tables directly, but you'll have to free either X or Y to do it. You could probably free Y, as it's just being used to read map data, so you could save it somewhere before copying the metatile to the buffer and restore it later.

There are some other things you could do, such as use a smaller buffer, like 64 bytes for only 1 full row of metatiles, and draw rows one by one. This will come in handy when you decide to make scrolling games, because then you'll only be able to update single columns or rows of metatiles. Making the rendering system row/column oriented will help when that time comes. And it's actually an advantage to use a smaller buffer, because you'll have more free RAM.

by on (#42524)
tokumaru wrote:
There are some other things you could do, such as use a smaller buffer, like 64 bytes for only 1 full row of metatiles, and draw rows one by one. This will come in handy when you decide to make scrolling games, because then you'll only be able to update single columns or rows of metatiles. Making the rendering system row/column oriented will help when that time comes. And it's actually an advantage to use a smaller buffer, because you'll have more free RAM.

But depending on how you decide to compress your maps and/or make the environment destructible, you may still need to keep a screen's worth of metatiles in RAM in order to do collision detection.

by on (#42527)
Celius wrote:
Even worse it BCC and BCS. I can never remember which one means Branch on Greater than or which means Branch on Less than.

The Apple IIGS Hardware Reference included a copy of the 65C816 data sheet. This defined BLT as an alias for BCC, and BGE as an alias for BCS. (6502.org has a newer version as a PDF; check page 38.) So yes, these mnemonics are canon, and ca65 can be set to recognize them.

by on (#42538)
Celius wrote:
Even worse it BCC and BCS. I can never remember which one means Branch on Greater than or which means Branch on Less than. I think Tokumaru said once that it's easy to remember because if the carry has been cleared, M > A because the 1 has been borrowed or something, and I loosely remember that.


sorry to continue the threadjack, but I remember it like this:

S is greater than C in the alphabet, so BCS is the "greater than" branch

by on (#42552)
EDIT: I have since figured out how to do the bottom portion of this post, though it is a bit ugly. I included a beq between the bcs and bne. Ugly I think, but works : P

ORIGINAL POST:
tokumaru wrote:
Instead of a table of routines, you could have 4 tables listing the tiles used by the metatiles <snip> OK, but the next optimization is actually to get rid of the tile_1, tile_2, tile_3, tile_4 variables. There is no need for them when you can read the tiles directly from the table. In your do_block_buffer routine, you could just read from the tile tables directly, but you'll have to free either X or Y to do it. You could probably free Y, as it's just being used to read map data, so you could save it somewhere before copying the metatile to the buffer and restore it later.


Ah, nice, that was pretty easy to implement, too!

Code:
tile_a:
   .byte $00,$01,$05,$07,$0b
tile_b:
   .byte $00,$02,$05,$08,$0c
tile_c:
   .byte $00,$03,$06,$09,$0b
tile_d:
   .byte $00,$04,$06,$0a,$0c

do_block_buffer:
   ldy get_block
   ldx metatile_index
   lda tile_a,y
   sta bg_buffer,x
   inx
   lda tile_b,y
   sta bg_buffer,x
   txa
   clc
   adc #31
   tax
   lda tile_c,y
   sta bg_buffer,x
   inx
   lda tile_d,y
   sta bg_buffer,x
   txa
   sec
   sbc #31
   tax
   and #%00011111
   bne :+
   txa
   clc
   adc #32
   tax
:   stx metatile_index
   rts

; preserving Y after getting the block
   ldy #$00
:   lda (address_temp_lo),y
   sta get_block
   tya
   pha

   jsr do_block_buffer
   pla
   tay
   iny
   cpy #$40
; more code blah blah


Quote:
There are some other things you could do, such as use a smaller buffer, like 64 bytes for only 1 full row of metatiles, and draw rows one by one. This will come in handy when you decide to make scrolling games, because then you'll only be able to update single columns or rows of metatiles. Making the rendering system row/column oriented will help when that time comes. And it's actually an advantage to use a smaller buffer, because you'll have more free RAM.


That sounds pretty interesting, but right now my focus is going to be on pulling the nametable in. Right now it's having problems doing both the top and middle portion at the same time, although it looks to me like it should be working fine. Correct me if I'm wrong, but should the BCS branch down to where I have it going, and the BNE should branch up, but if Y is 40, it should skip both of those and run the code to fill the top nametable?

Code:
   ldy #$00
:   lda (address_temp_lo),y
   sta get_block
   tya
   pha

;   jsr choose_block

   jsr do_block_buffer
   pla
   tay
   iny
   cpy #$40
   bcs :+
   bne :-

   inc address_temp_hi
   jsr do_top_nametable
:
   cpy #$80
   bne :--
   inc address_temp_hi
   jsr do_middle_nametable