Attribute / Tile

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Attribute / Tile
by on (#169206)
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.
Re: Attribute / Tile
by on (#169209)
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:
Code:
whichnt = ntaddr & 0x3C00
coarse_y = ntaddr & 0x03E0
coarse_x = ntaddr & 0x001F
attraddr = whichnt | 0x03C0 | (coarse_y >> 4) | (coarse_x >> 2)


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.
Re: Attribute / Tile
by on (#169212)
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:

Code:
  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


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.
Re: Attribute / Tile
by on (#169215)
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. :-)
Re: Attribute / Tile
by on (#169223)
How about this? I haven't tested it but it should work:

Code:
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
by on (#169276)
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!
Re: Attribute / Tile
by on (#169286)
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.

Quote:
Contrarily, when I put in values directly (#$23 into lo, #$C8 into hi)

You've got these backwards. #$23 is your high byte value and #$C8 is your low byte value.
Re: Attribute / Tile
by on (#169289)
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!
Re: Attribute / Tile
by on (#203806)
tepples wrote:
If you know C or Python or similar languages, you may understand this:
Code:
whichnt = ntaddr & 0x3C00
coarse_y = ntaddr & 0x03E0
coarse_x = ntaddr & 0x001F
attraddr = whichnt | 0x03C0 | (coarse_y >> 4) | (coarse_x >> 2)



Resurrecting this....unless I'm reading something wrong, I think this is off.

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:
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
Re: Attribute / Tile
by on (#203812)
I am having trouble following most of those mask values. Here's an alternative from the wiki (PPU Scrolling):
Code:
 tile address      = 0x2000 | (v & 0x0FFF)
 attribute address = 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07)


In particular I don't understand why these were that way:
  • 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
by on (#203813)
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:

Code:
attribute_y = ntaddr & 0x0380
attribute_x = ntaddr & 0x001C
attraddr = whichnt | 0x03C0 | (attribute_y >> 4) | (attribute_x >> 2)
and then the rest works out...
Re: Attribute / Tile
by on (#203814)
Well, for yet another explanation, the next paragraph behind that link I was quoting from is:
Code:
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
by on (#203815)
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
by on (#203822)
Yeah, I figured out a different way to calculate it, but wanted to make note of this seeming wrong, for any future readers :-)

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


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?
(I'm using 4 name tables, if that makes a difference)
Re: Attribute / Tile
by on (#203824)
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 :-)

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


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?
(I'm using 4 name tables, if that makes a difference)


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.
Re: Attribute / Tile
by on (#203826)
gauauu wrote:
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?
(I'm using 4 name tables, if that makes a difference)

Even if you do have the off-screen space for updates in all directions (which you do because you're using 4 name tables), you still have to deal with the fact each name table is 30 tiles tall, and that number is not divisible by 4, so in every other vertical name table the attributes of your 4x4 metatiles will be misaligned with the attribute table, meaning you'll have to shift and mix attributes from different metatiles (or a buffer in RAM) to form the final attribute bytes.

That is, unless you take the easy way out, which's truncating the bottommost row of metatiles in every screen, using only the top half, so that attributes never misalign. That's as easy as 8-way scrolling gets, but I think it's weird to pretend that sections of your level maps don't exist.
Re: Attribute / Tile
by on (#203828)
tokumaru wrote:
That is, unless you take the easy way out, which's truncating the bottommost row of metatiles in every screen, using only the top half, so that attributes never misalign. That's as easy as 8-way scrolling gets, but I think it's weird to pretend that sections of your level maps don't exist.


Heh, after a lot of thought and internal debate, that's exactly what I'm doing. I started writing the routines to do the mis-aligned metatiles, and said "forget this" -- so yeah, sections of my level maps won't actually exist :-/
Re: Attribute / Tile
by on (#203832)
Yeah, that simplifies the rendering code a lot, but slightly complicates the logic side, since now you'll also need to skip those rows when testing for collisions, meaning you'll probably need a few Y >= 240? checks here and there.
Re: Attribute / Tile
by on (#203878)
Yup. Most of the checks are pretty simple so far. The only annoying ones I've hit at this point are checking for characters that span the 240-px boundary. Might be worth tracking y2 for each character just to avoid having to recompute that at each collision check.
Re: Attribute / Tile
by on (#203987)
Use the hidden data to label your maps, mayhap~

Or use it to spell out another message.

I asked this question, or close to it.