Attribute updates
Moderator: Moderators
I used to combine things when my game design used 16x16 metatiles... I stored the attribute of each metatile using only 2 bits, as 6 bits is a lot to waste on repeated data while they could hold other stuff about the metatile. By masking the other 6 bits off, the lower 2 could be use as an index to load a full byte with those 2 repeated from a small table ($00, $55, $AA, $FF), and then I proceeded as Disch said.
It may be a little more trouble than directly loading the full byte (I'm really not gonna waste 75% of a lot of bytes), but is still cleaner than ASL'ing variable ammounts of times.
It may be a little more trouble than directly loading the full byte (I'm really not gonna waste 75% of a lot of bytes), but is still cleaner than ASL'ing variable ammounts of times.
Unless your attribute update subroutine always reads an attribute-square of tiles from the decoded map in RAM. Then it can always use shift twice. Or are you thinking of read-modify-writing individual bytes in the attribute table?Disch wrote:If you use 00, 01, 02, and 03, shifting the desired number of times will end up being more work. You'll either have to write 4 seperate routines (one to shift 0 times, one to shift twice, one 4 times, and one 6 tiems) -- or you'll have to deal with extra loops to make it dynamic.
The latter. Well... Read/modify/write the RAM copy of the attirbute tables. The acutal table in the PPU would never be read.tepples wrote:Unless your attribute update subroutine always reads an attribute-square of tiles from the decoded map in RAM. Then it can always use shift twice. Or are you thinking of read-modify-writing individual bytes in the attribute table?
I don't see how always shifting twice would work. I mean... eventually you'll have to pack four 2-bit values into a single byte. But even if you could somehow only always shift twice... wouldn't it make more sense to just have $00, $04, $08, $0C instead of $00-$03? That way you wouldn't have to shift at all.
I probably just don't understand your implimentation -- could you explain it more?
-
Celius
- Posts: 2159
- Joined: Sun Jun 05, 2005 2:04 pm
- Location: Minneapolis, Minnesota, United States
- Contact:
For my routine, I actually don't use any buffers to keep track of attributes. I take the data in RAM, and store an attribute column depending on the metatile values. I've said this before, I know. I actually have my attributes updated 1 pixel after the metatile columns are updated. If they were updated the same time as my columns, the NMI routine would last way too long. So I'm never doing my Attributes at the same time as my columns, because it takes too long. I have to go, sorry.
The trick is to always recompute the attributes for all four 16x16 pixel metatiles of an attribute byte at once. This handles the common case (scrolling) quickly and still makes the rarer case (poking tiles) acceptably fast.Disch wrote:I don't see how always shifting twice would work. I mean... eventually you'll have to pack four 2-bit values into a single byte.
- Read metatile in lower right corner of this attribute byte from the decompressed map
Look up attribute - Read metatile in lower left corner
Look up attribute
asl, asl, ora - Read metatile in upper right corner
Look up attribute
asl, asl, ora - Read metatile in upper left corner
Look up attribute
asl, asl, ora - Store attribute byte to attribute copy buffer
Yeah, that's great for side scrolling games.
As for vertical scrolling games... I wish there was a simple way to skip the bottom 16 scanlines of a name table, to avoid the half used attribute bytes at all. The trouble of using mapper IRQ's doesn't seem to pay off though...
*Think!*Think!* >_<
There has to be a simpler way of handling attributes... Heck, it doensn't have to be so simple, but it has to make enough sense to be programmed without a bunch of "IF's"... I hate code with lot's of branching. I like to find a general rule, that will work in all cases, even if it makes the one that would be the simplest situation a bit more complicated.
As for vertical scrolling games... I wish there was a simple way to skip the bottom 16 scanlines of a name table, to avoid the half used attribute bytes at all. The trouble of using mapper IRQ's doesn't seem to pay off though...
*Think!*Think!* >_<
There has to be a simpler way of handling attributes... Heck, it doensn't have to be so simple, but it has to make enough sense to be programmed without a bunch of "IF's"... I hate code with lot's of branching. I like to find a general rule, that will work in all cases, even if it makes the one that would be the simplest situation a bit more complicated.
I 100% agree with that, while as the programmer you don't always have the choise to how you will have to code some stuff.I like to find a general rule, that will work in all cases, even if it makes the one that would be the simplest situation a bit more complicated.
For attributes tables, the "easier way" to do it is to have a routine that update the color of a given metatile with its coordinates (x;y) and that convert it to attribute coordinate, combine it with already existing bits either by having a software copy of the attribute table, or by reading the real one trough $2007, and finally write the byte to $2007.
Having a table with only 2 bits value seems a dream in the game loop's point of view, and a nightmare in NMI's point of view, also agreeing that it would be a lot more RAM wasted. If you use two nametables of 15x16 nybbles, it will be 480 bytes overall, wich wastes nearly 2 pages of RAM. It may be good escient use, trough, but I think already combining the bytes in a RAM buffer should be do-able in most circonsences.
To read other attributes 2-bits values that aren't modified, if your game isn't too complicated, it is also possible to read the maps 15 metatiles forward/backward if scrolling horizontally and vertically, and 16 metatiles forward/backward if scrolling vetically when the attribute byte is on the scroll lines, and simply read the previous/following meatatile if it the attribute byte is totally scolled. This way may seem complicated, but it actually isn't as long as you only scroll one direction and you're not cheating with a copy of the attribute table in RAM. With more complicated games that merge textboxes and maps such as RPGs, I'd go with the attribute table copy thing, but for a simple game it'd go with that "read backward" method, even if it looks complicated it isn't and it avoid wasting memory / VBlank time.
I'd recommand it for all games scrolling in only one dimension. My game scrolls in both directions and does it that way, but it works with nybbles, and metatiles actually are 32x32, and the byte is splitted in nybbles or not in function of the vertical scrolling.
Useless, lumbering half-wits don't scare us.
-
Celius
- Posts: 2159
- Joined: Sun Jun 05, 2005 2:04 pm
- Location: Minneapolis, Minnesota, United States
- Contact:
You know, I was thinking of something. How about you have a copy of the attribute table in RAM. Then have $20 bytes in RAM. The $20 bytes of RAM will be used to store whole bytes that represent 2 bits for an attribute. Then when it's necissary, compress the $20 bytes, and store them on screen. The logic is actually simpler than I thought:
1. Decide how you want to designate palletes
ex. Having my metatiles arranged so $80-$83 is one, $84-$87 is another, etc. Then take the last two bits of the metatile ID to see the desired attribute bits.
$82 - Metatile $80, Tiles $80-$83, Attribits - 10
$7F - Metatile $7C, Tiles $7C-$7F, Attribits - 11
2. Store the desired pallete IDs in the $20 bytes of RAM
3. Compress them into $8 bytes
4. Update column/row when neccissary.
Does that make sense?
1. Decide how you want to designate palletes
ex. Having my metatiles arranged so $80-$83 is one, $84-$87 is another, etc. Then take the last two bits of the metatile ID to see the desired attribute bits.
$82 - Metatile $80, Tiles $80-$83, Attribits - 10
$7F - Metatile $7C, Tiles $7C-$7F, Attribits - 11
2. Store the desired pallete IDs in the $20 bytes of RAM
3. Compress them into $8 bytes
4. Update column/row when neccissary.
Does that make sense?
You need that extra RAM for collision and deformable geometry (e.g. Mario breaking bricks) anyway.Bregalad wrote:Having a table with only 2 bits value seems a dream in the game loop's point of view, and a nightmare in NMI's point of view, also agreeing that it would be a lot more RAM wasted. If you use two nametables of 15x16 nybbles, it will be 480 bytes overall, wich wastes nearly 2 pages of RAM.
No you don't. At least not for simple broken bricks. In my engine, "breakable brick"-kind of things are treated as objects, and some space in regular RAM is used to define states of all objects in the level. With only 256 bytes, you could define the "alive-dead" state of 2048 objects. That is enough to keep track of a lot of broken blocks, and not loading them again if they are "dead".tepples wrote:You need that extra RAM for collision and deformable geometry (e.g. Mario breaking bricks) anyway.
However, if you wanted the player to actually move blocks around and remember their position, then you'd need extra RAM.
-
Celius
- Posts: 2159
- Joined: Sun Jun 05, 2005 2:04 pm
- Location: Minneapolis, Minnesota, United States
- Contact:
Hi, I'm sorry to bring up this old thread, but it's better than starting a new one for the same thing.
I'm working on my universal scrolling/updating engine, and I have a problem (not a bug). I just have the engine updating the screen by storing columns and rows of attribute blocks (32x32 pixels) every 16 pixels. Since I'm using vertical mirroring, there is large color distortion on the top and bottom of the screen.
The problem is only when scrolling vertically, because there is no horizontal mirroring, so I can have all the color distortion I want off the sides of the screen. So when scrolling vertically, I need to update each row of attributes 16 pixels at a time, keeping the other half that's visible on the other end of the screen the same colors. So I'll need a table in RAM that holds $48 values (Not $40, because sometimes there are 33 tiles being displayed on screen in each row, and I need to have an attribute for that extra tile for every row of attributes.), and I'll need to shift that table a byte left, or right, or 8 bytes left or right (If I press up or down) when I cross 32 pixels.
Does anyone have any ideas on how to either shift the table without taking 800 years, or any other suggestions on how to go about updating attributes 16 pixels at a time scrolling in the direction of the mirroring?
I'm working on my universal scrolling/updating engine, and I have a problem (not a bug). I just have the engine updating the screen by storing columns and rows of attribute blocks (32x32 pixels) every 16 pixels. Since I'm using vertical mirroring, there is large color distortion on the top and bottom of the screen.
The problem is only when scrolling vertically, because there is no horizontal mirroring, so I can have all the color distortion I want off the sides of the screen. So when scrolling vertically, I need to update each row of attributes 16 pixels at a time, keeping the other half that's visible on the other end of the screen the same colors. So I'll need a table in RAM that holds $48 values (Not $40, because sometimes there are 33 tiles being displayed on screen in each row, and I need to have an attribute for that extra tile for every row of attributes.), and I'll need to shift that table a byte left, or right, or 8 bytes left or right (If I press up or down) when I cross 32 pixels.
Does anyone have any ideas on how to either shift the table without taking 800 years, or any other suggestions on how to go about updating attributes 16 pixels at a time scrolling in the direction of the mirroring?
16 pixels is the hardware minimum, and split an attribute nybble isn't too bad (you just need to work with 4 bits a time) so you can work with vertical 16 pixel blocks and horizontal 32 pixels blocks. Else, just work with 16x16 blocks everywhere, and when you update row/colums of meatiles, just be sure to refresh just a nibble at the same time (by keeping a copy of the attribute table in RAM for example).
-
Celius
- Posts: 2159
- Joined: Sun Jun 05, 2005 2:04 pm
- Location: Minneapolis, Minnesota, United States
- Contact:
That was my plan all along. But take a look at this:
$00,$55,$AA,$FF,$1B,$09,$30,$44,$33
This represents a 9 byte long row of attributes. It's 9 bytes, because if you scroll right 4 pixels from the very beggining nametable, You'll be seeing half of the tile at $2000 and half of the tile at $2400, as well as all of them in between. So there are 33 different tiles on screen at once, and I need to have the attribute information for the extra tile on the edge of the screen. But anyways, when I scroll 32 pixels to the right, this row will need to changed. It needs to shift one whole byte over. So $00 at the beggining will be sent to the garbage, and an extra byte will have to be added at the end. Say the attribute byte for the next column is $EF. I need to then add that after $33. So it will look like this:
$55,$AA,$FF,$1B,$09,$30,$44,$33,$EF
This is an easy concept, but to do, it would take a very long time. Does anyone know of a way I could do this without taking a very long time?
$00,$55,$AA,$FF,$1B,$09,$30,$44,$33
This represents a 9 byte long row of attributes. It's 9 bytes, because if you scroll right 4 pixels from the very beggining nametable, You'll be seeing half of the tile at $2000 and half of the tile at $2400, as well as all of them in between. So there are 33 different tiles on screen at once, and I need to have the attribute information for the extra tile on the edge of the screen. But anyways, when I scroll 32 pixels to the right, this row will need to changed. It needs to shift one whole byte over. So $00 at the beggining will be sent to the garbage, and an extra byte will have to be added at the end. Say the attribute byte for the next column is $EF. I need to then add that after $33. So it will look like this:
$55,$AA,$FF,$1B,$09,$30,$44,$33,$EF
This is an easy concept, but to do, it would take a very long time. Does anyone know of a way I could do this without taking a very long time?
How long is a "very long time"?
What you are describing involves shifting a 72-byte buffer by a distance of 1 byte. The following takes fewer than 1100 cycles per my count, and would take even fewer if unrolled:
What you are describing involves shifting a 72-byte buffer by a distance of 1 byte. The following takes fewer than 1100 cycles per my count, and would take even fewer if unrolled:
Code: Select all
ldx #0
@loop:
lda buffer+1,x
sta buffer,x
inx
cpx #71
bcc @loop