Attribute / Tile

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

JoeGtake2
Posts: 333
Joined: Tue Jul 01, 2014 4:02 pm

Attribute / Tile

Post by JoeGtake2 »

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.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Attribute / Tile

Post by tepples »

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: Select all

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.
User avatar
darryl.revok
Posts: 520
Joined: Sat Jul 25, 2015 1:22 pm

Re: Attribute / Tile

Post by darryl.revok »

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: 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
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.
JoeGtake2
Posts: 333
Joined: Tue Jul 01, 2014 4:02 pm

Re: Attribute / Tile

Post by JoeGtake2 »

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. :-)
User avatar
darryl.revok
Posts: 520
Joined: Sat Jul 25, 2015 1:22 pm

Re: Attribute / Tile

Post by darryl.revok »

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
JoeGtake2
Posts: 333
Joined: Tue Jul 01, 2014 4:02 pm

Re: Attribute / Tile

Post by JoeGtake2 »

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!
User avatar
darryl.revok
Posts: 520
Joined: Sat Jul 25, 2015 1:22 pm

Re: Attribute / Tile

Post by darryl.revok »

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.
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.
JoeGtake2
Posts: 333
Joined: Tue Jul 01, 2014 4:02 pm

Re: Attribute / Tile

Post by JoeGtake2 »

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!
User avatar
gauauu
Posts: 779
Joined: Sat Jan 09, 2016 9:21 pm
Location: Central Illinois, USA
Contact:

Re: Attribute / Tile

Post by gauauu »

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)
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: 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
User avatar
rainwarrior
Posts: 8735
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Attribute / Tile

Post by rainwarrior »

I am having trouble following most of those mask values. Here's an alternative from the wiki (PPU Scrolling):

Code: Select all

 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?)
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Attribute / Tile

Post by lidnariq »

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: Select all

attribute_y = ntaddr & 0x0380
attribute_x = ntaddr & 0x001C
attraddr = whichnt | 0x03C0 | (attribute_y >> 4) | (attribute_x >> 2)
and then the rest works out...
User avatar
rainwarrior
Posts: 8735
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Attribute / Tile

Post by rainwarrior »

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
AWJ
Posts: 433
Joined: Mon Nov 10, 2008 3:09 pm

Re: Attribute / Tile

Post by AWJ »

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.
User avatar
gauauu
Posts: 779
Joined: Sat Jan 09, 2016 9:21 pm
Location: Central Illinois, USA
Contact:

Re: Attribute / Tile

Post by gauauu »

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)
AWJ
Posts: 433
Joined: Mon Nov 10, 2008 3:09 pm

Re: Attribute / Tile

Post by AWJ »

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.
Post Reply