Attribute / Tile
Moderator: Moderators
Attribute / Tile
Hey all - first, I completely understand the relationship of attributes to tiles, understanding how they sit at $23c0, and how they're organized. In a routine, however, I'm attempting to find the easiest way to find the attribute group associated with the a given 16bit tile address. I get that there are 64 'attribute quad bytes' essentially, the last one being 'half'...and while there are 960 tiles...i can sort of think of the ratio between 1024 to 64...dividing the two byte address off the tile in question by 16 should give me the correct 'attribute quad' beyond $23c0...then I could evaluate down from there to figure which of the quads the tile is in...
Does this seem sound? Does anyone have a good or better method for this?
Just running through a thought experiment.
Thanks.
Does this seem sound? Does anyone have a good or better method for this?
Just running through a thought experiment.
Thanks.
Re: Attribute / Tile
It's not just a division by 16, but you're on the right track.
Each attribute byte covers a 4 tile by 4 tile area. So you need to divide the horizontal part (bits 4-0) of the tile address by 4 (the width of the area) and the vertical part (bits 9-5) by 4*4 = 16 (the area of the area).
If you know C or Python or similar languages, you may understand this:
In 6502 assembly language, the best way to calculate this depends largely on the kind of update that you plan to do at any given time.
Each attribute byte covers a 4 tile by 4 tile area. So you need to divide the horizontal part (bits 4-0) of the tile address by 4 (the width of the area) and the vertical part (bits 9-5) by 4*4 = 16 (the area of the area).
If you know C or Python or similar languages, you may understand this:
Code: Select all
whichnt = ntaddr & 0x3C00
coarse_y = ntaddr & 0x03E0
coarse_x = ntaddr & 0x001F
attraddr = whichnt | 0x03C0 | (coarse_y >> 4) | (coarse_x >> 2)
- darryl.revok
- Posts: 520
- Joined: Sat Jul 25, 2015 1:22 pm
Re: Attribute / Tile
If I understand what you're asking, I believe it varies on implementation.
If you would let me know what you're aiming for (ie. scrolling in a horizontal row, scrolling in a vertical column, animating a tile already in the nametable) I can be more specific. However, since you're usually updating a group of tiles and attributes at one time, taking the nametable address of a tile, and then calculating the attribute nametable address from that, is usually not the most productive method.
It's better I think, to calculate a base address for tiles and a base address for attributes from a common variable, and then go from there with each. You can even share some of the math between the calculations.
For example, you'll usually be starting with a scroll position to find tiles from your map, so I did something like this:
The math is pretty simple when it's just for rows or columns, and not 2-axis scrolling. From there you can use the PPU increment to fill out a column, (attribute columns would require manual addressing due to lack of +8 inc) with no need to calculate addresses on a tile-per-tile basis.
Hope this helps. Feel free to ask more questions.
If you would let me know what you're aiming for (ie. scrolling in a horizontal row, scrolling in a vertical column, animating a tile already in the nametable) I can be more specific. However, since you're usually updating a group of tiles and attributes at one time, taking the nametable address of a tile, and then calculating the attribute nametable address from that, is usually not the most productive method.
It's better I think, to calculate a base address for tiles and a base address for attributes from a common variable, and then go from there with each. You can even share some of the math between the calculations.
For example, you'll usually be starting with a scroll position to find tiles from your map, so I did something like this:
Code: Select all
LDA hScrollLo
LSR
LSR
LSR ; Number of tiles which have been scrolled horizontally in nametable
STA nametableColumnLo
LSR
LSR ; Number of attributes which have been scrolled horizontally
CLC
ADC #$C0
STA nametableColumnAttributesLo, x
Hope this helps. Feel free to ask more questions.
Re: Attribute / Tile
Actually not even for scrolling. Just for spot updates to first nametable when screen is turned off.
Just trying to figure out how to extrapolate the attribute address from the figured tile address.
Just trying to figure out how to extrapolate the attribute address from the figured tile address.
- darryl.revok
- Posts: 520
- Joined: Sat Jul 25, 2015 1:22 pm
Re: Attribute / Tile
How about this? I haven't tested it but it should work:
Code: Select all
LDA nametableTileLo
LSR
LSR
CMP #$20
AND #%00000111
STA temp
LDA nametableTileHi
ROL
ASL
ASL
ASL
ORA #$C0
ORA temp
STA nametableAttributesLo
LDA nametableTileHi
ORA #$03
STA nametableAttributesHi
Re: Attribute / Tile
Should nametableAttributesLo / Hi then give me the address to plug into 2006 to update the attribute? If so, this did not work. What I tried with the data was just wrote ntaLo to 2006, wrote ntaHi to 2006, and then wrote arbitrary values (#%01010101) to $2007 to see a change. There is no change.
Contrarily, when I put in values directly (#$23 into lo, #$C8 into hi), I get the expected results, so I know the routine is being called, and I know that it is set up right...it's just that the routine isn't getting the correct tile address.
Any thoughts?
Thanks!
Contrarily, when I put in values directly (#$23 into lo, #$C8 into hi), I get the expected results, so I know the routine is being called, and I know that it is set up right...it's just that the routine isn't getting the correct tile address.
Any thoughts?
Thanks!
- darryl.revok
- Posts: 520
- Joined: Sat Jul 25, 2015 1:22 pm
Re: Attribute / Tile
Hmmm. I tested it and it's working properly for me.
From what you described, I think it's the order in which you're giving the data to the PPU.
Unlike the rest of the hardware, the PPU expects to receive the high byte of the address first, so write nametableAttributesHi to $2006, then write nametableAttributesLo to $2006 and write your attribute value to $2007 and it should be good to go.
From what you described, I think it's the order in which you're giving the data to the PPU.
Unlike the rest of the hardware, the PPU expects to receive the high byte of the address first, so write nametableAttributesHi to $2006, then write nametableAttributesLo to $2006 and write your attribute value to $2007 and it should be good to go.
You've got these backwards. #$23 is your high byte value and #$C8 is your low byte value.Contrarily, when I put in values directly (#$23 into lo, #$C8 into hi)
Re: Attribute / Tile
Yes sorry that was a mis-type.
Hmm, alright I'll run through and make sure of all my byte agreements, and make sure there aren't any variables mucking things up. I appreciate your help and testing it out!
*EDIT*
Just a gremlin, I guess. Went through it line by line, couldn't find the issue. Erased and rewrote...worked fine.
Thanks!
Hmm, alright I'll run through and make sure of all my byte agreements, and make sure there aren't any variables mucking things up. I appreciate your help and testing it out!
*EDIT*
Just a gremlin, I guess. Went through it line by line, couldn't find the issue. Erased and rewrote...worked fine.
Thanks!
Re: Attribute / Tile
Resurrecting this....unless I'm reading something wrong, I think this is off.tepples wrote: If you know C or Python or similar languages, you may understand this:Code: Select all
whichnt = ntaddr & 0x3C00 coarse_y = ntaddr & 0x03E0 coarse_x = ntaddr & 0x001F attraddr = whichnt | 0x03C0 | (coarse_y >> 4) | (coarse_x >> 2)
For PPU address 0x2820 (the leftmost column of the nametable, 2nd row), the attribute address should be 0x2BCO.
But this pseudocode gives 0x2BC2. course_y in my example would be 20, so course_y >> 4 is 2, which is not what we want, which is what ends up with that 2 at the end.
Please let me know if I'm mistaken, I'm probably overlooking something.
Code: Select all
ntaddr = 0x2820
whichnt = ntaddr & 0x3C00 ;gives 2800, correct
coarse_y = ntaddr & 0x03E0 ;gives 20, correct
coarse_x = ntaddr & 0x001F ; gives 0, correct
attraddr = whichnt | 0x03C0 ;2BC0, so far so good
| (coarse_y >> 4) ; 2BC0 | 2 = 2BC2, seems wrong?.
| (coarse_x >> 2) ; | 0, not important here
My games: http://www.bitethechili.com
- rainwarrior
- Posts: 8735
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: Attribute / Tile
I am having trouble following most of those mask values. Here's an alternative from the wiki (PPU Scrolling):
In particular I don't understand why these were that way:
Code: Select all
tile address = 0x2000 | (v & 0x0FFF)
attribute address = 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07)
- whichnt: $0C00 not $3C00 (sort of the same, but $0XXX,$1XXX,$3XXX aren't accessible nametables)
- coarse_y: $0380 not $03E0 (?)
- coarse_x: $001C not $001F (doesn't matter, bits are discarded anyway, but why keep them?)
Re: Attribute / Tile
coarse_x and coarse_y are clearly nametable granularity. (five bits each)
Attribute table is coarser by two more bits; just three bits per axis. I guess if you wanted to just add a few more variables to clarify:
and then the rest works out...
Attribute table is coarser by two more bits; just three bits per axis. I guess if you wanted to just add a few more variables to clarify:
Code: Select all
attribute_y = ntaddr & 0x0380
attribute_x = ntaddr & 0x001C
attraddr = whichnt | 0x03C0 | (attribute_y >> 4) | (attribute_x >> 2)
- rainwarrior
- Posts: 8735
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: Attribute / Tile
Well, for yet another explanation, the next paragraph behind that link I was quoting from is:
Code: Select all
The low 12 bits of the attribute address are composed in the following way:
NN 1111 YYY XXX
|| |||| ||| +++-- high 3 bits of coarse X (x/4)
|| |||| +++------ high 3 bits of coarse Y (y/4)
|| ++++---------- attribute offset (960 bytes)
++--------------- nametable select
Re: Attribute / Tile
One thing to keep in mind is that unless your game uses 4x4 metatiles and only scrolls along one axis at a time, you're going to have to read-modify-write attribute table bytes in order to update individual 2x2 tile cells. Since accessing PPU memory this way is extremely inefficient, you'll probably want to shadow the attribute tables in work RAM.
Re: Attribute / Tile
Yeah, I figured out a different way to calculate it, but wanted to make note of this seeming wrong, for any future readers
(I'm using 4 name tables, if that makes a difference)
Well, I am using 4x4 metatiles. But doing free all-directional scrolling - it's not obvious to me why I need to read the attribute tables for that, am I missing something?AJW wrote:One thing to keep in mind is that unless your game uses 4x4 metatiles and only scrolls along one axis at a time
(I'm using 4 name tables, if that makes a difference)
My games: http://www.bitethechili.com
Re: Attribute / Tile
If you're doing all-directional scrolling without 4-screen mirroring, you're going to have fewer than 32 pixels of offscreen area in at least one axis, so you can't just draw an entire 32x32 metatile at a time or you'll produce visible garbage.gauauu wrote:Yeah, I figured out a different way to calculate it, but wanted to make note of this seeming wrong, for any future readers
Well, I am using 4x4 metatiles. But doing free all-directional scrolling - it's not obvious to me why I need to read the attribute tables for that, am I missing something?AJW wrote:One thing to keep in mind is that unless your game uses 4x4 metatiles and only scrolls along one axis at a time
(I'm using 4 name tables, if that makes a difference)