troubles with updating the attributes correctly

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

crystalmoon
Posts: 21
Joined: Wed Aug 23, 2023 12:45 am
Contact:

troubles with updating the attributes correctly

Post by crystalmoon »

somewhere in this code is a bug, I can't spot it, but yet it outputs very incorrectly.

Code: Select all

; src - tilemap source
DrawTilemap:
	ldy #$0
	lda #$20
	sta scratch1+1
	lda #$00
	sta scratch1
	sta scratch1+3
	lda #$C0
	sta scratch1+2
	lda #$04
	sta scratch1+4
	tya
	tax
@loop:
	lda (src),y
	cmp #$FF
	beq @ret
	tax
	lda scratch1+1
	sta $2006
	lda scratch1
	sta $2006
	lda Metatiles1_TL,x
	sta $2007
	lda Metatiles1_TR,x
	sta $2007
	lda scratch1+1
	sta $2006
	lda scratch1
	clc
	adc #$20
	sta $2006
	lda Metatiles1_BL,x
	sta $2007
	lda Metatiles1_BR,x
	sta $2007
	lda Metatiles1_Pal,x
	and #%11
	clc
	adc scratch1+3
	rol a
	rol a
	adc #0
	sta scratch1+3
	dec scratch1+4
	bne :+
	lda #4
	sta scratch1+4
	lda #$23
	sta $2006
	lda scratch1+2
	sta $2006
	lda scratch1+3
	sta $2007
	lda #0
	sta scratch1+3
	inc scratch1+2
:
	inc scratch1
	inc scratch1
	lda scratch1
	and #$1F
	bne @cont
	lda scratch1
	clc
	adc #$20
	sta scratch1
	bcc @cont
	inc scratch1+1

@cont:
	iny
	bne @loop

@ret:
	rts
I've attached a screenshot depicting how it looks. it should not be looking this way, but I don't have a clue as to how to fix it.
I can attach my whole codebase, if necessary. thank you for your help!
Attachments
rom_018.png
rom_018.png (4.52 KiB) Viewed 1186 times
User avatar
t1lt
Posts: 41
Joined: Sun Oct 01, 2023 7:40 am

Re: troubles with updating the attributes correctly

Post by t1lt »

Hi @crystalmoon for me whenever I've had these kinds of mystery PPU issues, usually it was because I was trying to read/write to it while rendering. :shock:
So just to double-check, I think this code should be run during blanking or with rendering off...

For the code you attach itself, the only line that stands out to me as not so obvious is this one:

Code: Select all

	lda Metatiles1_Pal,x
	and #%11
	clc
	adc scratch1+3
	rol a
	rol a
	adc #0 ; <- what is this for?
	sta scratch1+3
	dec scratch1+4
	bne :+
PPU issues can be tricky, to really troubleshoot further I think the best would be to run the whole program in a debugger like Mesen etc. if you haven't already
crystalmoon
Posts: 21
Joined: Wed Aug 23, 2023 12:45 am
Contact:

Re: troubles with updating the attributes correctly

Post by crystalmoon »

t1lt wrote: Sun Nov 24, 2024 7:06 am Hi @crystalmoon for me whenever I've had these kinds of mystery PPU issues, usually it was because I was trying to read/write to it while rendering. :shock:
So just to double-check, I think this code should be run during blanking or with rendering off...

For the code you attach itself, the only line that stands out to me as not so obvious is this one:

Code: Select all

	lda Metatiles1_Pal,x
	and #%11
	clc
	adc scratch1+3
	rol a
	rol a
	adc #0 ; <- what is this for?
	sta scratch1+3
	dec scratch1+4
	bne :+
PPU issues can be tricky, to really troubleshoot further I think the best would be to run the whole program in a debugger like Mesen etc. if you haven't already
yes, when the code is run the PPU rendering is turned off for both BG and sprites. I also disabled NMI so it doesn't interfere.
that `adc #0` was a bandaid fix, because without it it always made the top left tile green when it should've been brown in my test scene.
Joe
Posts: 678
Joined: Mon Apr 01, 2013 11:17 pm

Re: troubles with updating the attributes correctly

Post by Joe »

Does it work correctly if you add a second adc #0 between the two rol a instructions?
crystalmoon
Posts: 21
Joined: Wed Aug 23, 2023 12:45 am
Contact:

Re: troubles with updating the attributes correctly

Post by crystalmoon »

Joe wrote: Sun Nov 24, 2024 2:56 pm Does it work correctly if you add a second adc #0 between the two rol a instructions?
I can't check right now, but tell you what, I don't think it's going to work. there's a fundamental mismatch between how I make the attribute bytes up vs how they actually should be— I'm drawing metatiles row by row, and that's also how I scan for the attributes, but of course they work in 32x32 squares for each byte.
User avatar
tokumaru
Posts: 12499
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: troubles with updating the attributes correctly

Post by tokumaru »

For attributes, you either store them "whole" (pre computed alongside the map data) or you use some kind of buffer in RAM (at least 8 bytes for an entire row, possibly more if scrolling, etc.).

Pre computing is obviously easier for the program, but less versatile if you scroll vertically (due to the 32x32-pixel grid not aligning perfectly with the 30-tile tall name table) or you need to modify individual 16x16-pixel areas dynamically (such as when destroying environments or removing collectable items).

When using a buffer, you have two options:

1- Use the lowest bit from each of the X and Y coordinates of the metatile being updated to form a 2-bit index (possible values are 0 to 3) you can use to access bitmasks to clear the necessary bits in the metatile's attributes and in the buffered byte before combining them. The next 3 bits (bits 1 through 3) of the X coordinate will tell you which of the 8 bytes in the buffer to use. The other bits of the Y coordinate don't matter if you're buffering only 1 row.

2- If you are building the entire screen at once, meaning you know you'll iterate over all metatiles on the screen, you can fill the buffer progressively 2 bits at a time with each metatile and send all 8 bytes to VRAM every 2 rows of metatiles. According to the wiki, the format of an attribute byte is this:

Code: Select all

,---+---+---+---.
|   |   |   |   |
+ D1-D0 + D3-D2 +
|   |   |   |   |
+---+---+---+---+
|   |   |   |   |
+ D5-D4 + D7-D6 +
|   |   |   |   |
`---+---+---+---'
If you're scanning the metatiles from left to right, top to bottom, you want to shift the attributes into the buffer from the left, so that when you're done shifting them to the right, the bits for the top left metatile will be at positions 0-1, as they should.

In this case I would probably unroll the metatile drawing code a bit so it draws 2 metatiles on each iteration, inserting the attribute bits into the current buffer position from left to right, and then incrementing the buffer position by 1. For the next row, reset the buffer position in order to fill the remaining half of the attribute bytes. After this second row is done, upload all 8 attribute bytes to VRAM.
crystalmoon
Posts: 21
Joined: Wed Aug 23, 2023 12:45 am
Contact:

Re: troubles with updating the attributes correctly

Post by crystalmoon »

tokumaru wrote: Mon Nov 25, 2024 9:36 am For attributes, you either store them "whole" (pre computed alongside the map data) or you use some kind of buffer in RAM (at least 8 bytes for an entire row, possibly more if scrolling, etc.).

Pre computing is obviously easier for the program, but less versatile if you scroll vertically (due to the 32x32-pixel grid not aligning perfectly with the 30-tile tall name table) or you need to modify individual 16x16-pixel areas dynamically (such as when destroying environments or removing collectable items).

When using a buffer, you have two options:

1- Use the lowest bit from each of the X and Y coordinates of the metatile being updated to form a 2-bit index (possible values are 0 to 3) you can use to access bitmasks to clear the necessary bits in the metatile's attributes and in the buffered byte before combining them. The next 3 bits (bits 1 through 3) of the X coordinate will tell you which of the 8 bytes in the buffer to use. The other bits of the Y coordinate don't matter if you're buffering only 1 row.

2- If you are building the entire screen at once, meaning you know you'll iterate over all metatiles on the screen, you can fill the buffer progressively 2 bits at a time with each metatile and send all 8 bytes to VRAM every 2 rows of metatiles. According to the wiki, the format of an attribute byte is this:

Code: Select all

,---+---+---+---.
|   |   |   |   |
+ D1-D0 + D3-D2 +
|   |   |   |   |
+---+---+---+---+
|   |   |   |   |
+ D5-D4 + D7-D6 +
|   |   |   |   |
`---+---+---+---'
If you're scanning the metatiles from left to right, top to bottom, you want to shift the attributes into the buffer from the left, so that when you're done shifting them to the right, the bits for the top left metatile will be at positions 0-1, as they should.

In this case I would probably unroll the metatile drawing code a bit so it draws 2 metatiles on each iteration, inserting the attribute bits into the current buffer position from left to right, and then incrementing the buffer position by 1. For the next row, reset the buffer position in order to fill the remaining half of the attribute bytes. After this second row is done, upload all 8 attribute bytes to VRAM.
I decided to go with the buffer idea, and while the upload-to-VRAM part seems to work smoothly, the actual combining part not-so-much.

Code: Select all

@loop:
	lda (src),y
	cmp #$FF
	bne :+
	rts
:
	tax
	lda NametableAddrHi
	sta $2006
	lda NametableAddrLo
	sta $2006
	lda Metatiles1_TL,x
	sta $2007
	lda Metatiles1_TR,x
	sta $2007
	lda NametableAddrHi
	sta $2006
	lda NametableAddrLo
	clc
	adc #$20
	sta $2006
	lda Metatiles1_BL,x
	sta $2007
	lda Metatiles1_BR,x
	sta $2007
	lda Metatiles1_Pal,x
	; begin attribute combination code
	pha
	lda AttributeX
	and #1
	rol a
	ora AttributeY
	and #3
	tax
	lda AttributeBitmaskTable,x
	sta AttributeBitmask
	lda AttributeX
	and #%11111100
	ror a
	ror a
	tax
	pla
	and AttributeBitmask
	ora attr_scratch,x
	sta attr_scratch,x
	; end attribute combination code
	inc NametableAddrLo
	inc NametableAddrLo
	inc AttributeX
	lda NametableAddrLo
	and #$3F
	beq :+
	jsr @upload_to_vram
:
	lda NametableAddrLo
	and #$1F
	bne @cont
	lda NametableAddrLo
	clc
	adc #$20
	sta NametableAddrLo
	lda #0
	sta AttributeX
	inc AttributeY
	bcc @cont
	inc NametableAddrHi
if there's any obvious bug here I apologise for bothering, I'm just tired of staring at code over the last few days besides the small break I took =P
User avatar
tokumaru
Posts: 12499
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: troubles with updating the attributes correctly

Post by tokumaru »

Sorry for not replying about your revised code, but I personally have a bit of a hard time debugging written code like that, especially if I didn't write it and there are no comments... Even with my own code I hardly ever debug it by just looking at it, I'm much more likely to step through it in a good debugging emulator like Mesen where I can see at each step if each instruction has the expected outcome, making it really easy to detect when exactly things go wrong.

I suggest you debug your code the same way - first you set up a very simple scene where every attribute byte is supposed to follow a very simple pattern (such as 11-10-01-00) and then you step through this code to verify what's preventing the expected bits from getting shifted into the correct bytes.
crystalmoon
Posts: 21
Joined: Wed Aug 23, 2023 12:45 am
Contact:

Re: troubles with updating the attributes correctly

Post by crystalmoon »

tokumaru wrote: Fri Nov 29, 2024 8:22 am Sorry for not replying about your revised code, but I personally have a bit of a hard time debugging written code like that, especially if I didn't write it and there are no comments... Even with my own code I hardly ever debug it by just looking at it, I'm much more likely to step through it in a good debugging emulator like Mesen where I can see at each step if each instruction has the expected outcome, making it really easy to detect when exactly things go wrong.

I suggest you debug your code the same way - first you set up a very simple scene where every attribute byte is supposed to follow a very simple pattern (such as 11-10-01-00) and then you step through this code to verify what's preventing the expected bits from getting shifted into the correct bytes.
thanks for the reply.
I have taken your advice of putting an easy pattern. I tried %00011011, the inverse of your suggestion. I'd gotten the code to be almost correct (except for the top left 16x block) in the test map, but it didn't work as well on my other, non-test map. I'm really tired and had not the greatest of days today (I don't want to sound so pretentious, or whatever you'd call it, but I was almost about to pull my hair out over this) and don't really want to stare at this code right now.

here's how the test pattern should look in game, done with memory editing:
rom_021.png
rom_021.png (5.27 KiB) Viewed 552 times
here's how it actually looks upon booting:
rom_020.png
rom_020.png (4.9 KiB) Viewed 552 times
it should have the index "1B" for all of the bytes, but it has the index "B0" instead.

here's my updated code, commented this time.

Code: Select all

; src - tilemap source
DrawTilemap:
.scope
	NametableAddrLo		:= scratch1
	NametableAddrHi		:= scratch1+1
	AttributeAddrLo		:= scratch1+2
	AttributeMockup		:= scratch1+3
	AttributeCountr		:= scratch1+4
	AttributeX			:= scratch1+5
	AttributeY			:= scratch1+6
	AttributeBitmask	:= scratch1+7

	lda #$20
	sta NametableAddrHi

	lda #$C0
	sta AttributeAddrLo ; set up attribute address pointer
	lda #$00
	sta NametableAddrLo ; set up nametable address pointer
	sta AttributeMockup ; set up attribute mockup counter
	sta AttributeX ; set up attribute x
	sta AttributeY ; set up attribute y
	ldx #8 ; loop, clearing attribute scratch ram
	tay
:
	sta attr_scratch,y
	iny
	dex
	bne :-

	ldy #$0 ; clear the registers
	tya
	tax
@loop:
	lda (src),y ; get the current metatile
	cmp #$FF ; is it $FF (end of list)?
	bne :+ ; if not, branch
	rts ; end of subroutine
:
	tax ; use current metatile as index
	lda NametableAddrHi ; set up PPU address
	sta $2006
	lda NametableAddrLo
	sta $2006
	lda Metatiles1_TL,x ; get top left of the metatile
	sta $2007
	lda Metatiles1_TR,x ; get the top right of the metatile
	sta $2007
	lda NametableAddrHi ; add $20 to address
	sta $2006
	lda NametableAddrLo
	clc
	adc #$20
	sta $2006
	lda Metatiles1_BL,x ; get the bottom left of the metatile
	sta $2007
	lda Metatiles1_BR,x ; get the bottom right of the metatile
	sta $2007
	lda Metatiles1_Pal,x ; get the palette of the metatile... now the fun begins =|
	pha ; back up the palette in the stack
	lda AttributeY ; load the attribute Y counter
	and #1 ; get the LSB
	rol a ; multiply by 2
	sta scratch_vacc ; store it to and it later
	lda AttributeX ; load the attribute X counter
	and #1 ; get the LSB
	ora scratch_vacc ; or with attribute Y
	tax ; transfer it to X
	lda AttributeBitmaskTable,x ; load the correct bitmask
	sta AttributeBitmask ; store it in the scratch byte
	lda AttributeBitshiftTable,x ; load the correct bitshift amount
	sta scratch_vacc ; back it up
	lda AttributeX ; load the attribute X byte
	and #%1110 ; get rid of the LSB
	ror a ; shift it right
	tax ; use it as an index
	pla ; get the current metatile palette back
	stx AttributeMockup ; back up the index
	ldx scratch_vacc ; load the bitshift counter
	beq @skip_bitshifts ; if there's no bitshifts to do, branch
:
	ror a ; bit shift it once
	dex ; is there more bitshifting to do?
	bne :- ; if not, branch
@skip_bitshifts:
	ldx AttributeMockup ; reload index
	ora attr_scratch,x ; combine it with the correct attribute scratch byte
	sta attr_scratch,x
	inc NametableAddrLo ; increment the low nametable addr by 2
	inc NametableAddrLo
	inc AttributeX ; increment the attribute X counter
	lda NametableAddrLo ; have we done 2 rows yet?
	and #$3F
	beq :+ ; if not, branch
	jsr @upload_to_vram ; upload the palette to VRAM
:
	lda NametableAddrLo ; have we finished a row now?
	and #$1F
	bne @cont ; if not, branch
	lda NametableAddrLo ; add $20 to the nametable address to avoid drawing over the bottom row of tiles
	clc
	adc #$20
	sta NametableAddrLo
	lda #0 ; reset the attribute X counter
	sta AttributeX
	inc AttributeY ; increment the attribute Y counter
	bcc @cont ; if carry, increment the nametable high address
	inc NametableAddrHi

@cont:
	iny ; increment metatile pointer index
	beq @ret ; if we've reached 255 tiles, return
	jmp @loop ; else, branch

@ret:
	rts

@upload_to_vram:
	lda #$23 ; set up to load the attributes in the PPU
	sta $2006
	lda AttributeAddrLo
	sta $2006
	lda #8 ; load the counter for the attributes
	sta AttributeCountr
	ldx #0
@loop_:
	lda attr_scratch,x ; load the scratch byte
	sta $2007 ; write it to the PPU
	inx ; increase the index
	inc AttributeAddrLo ; increment the low attribute address
	lda AttributeAddrLo
	cmp #$C0 ; has it overflowed?
	bcs :+ ; if not, branch
	lda #$C0 ; reset the address back to $23C0
	sta AttributeAddrLo
:
	dec AttributeCountr ; decrement the counter
	bne @loop_ ; if it isn't the end yet, loop
	rts ; otherwise, return

AttributeBitmaskTable:
	.byte %00000011
	.byte %00001100
	.byte %00110000
	.byte %11000000

AttributeBitshiftTable:
	.byte 5, 3, 1, 0
.endscope
I've also attached the ROM here. thanks for the help, and really sorry to bother!
Attachments
rom.nes
(136.02 KiB) Downloaded 17 times
User avatar
Dwedit
Posts: 5069
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: troubles with updating the attributes correctly

Post by Dwedit »

Why are there 8 writes to attribute memory for every 4 writes to map memory? That's very wrong. If you are updating one 16x16 block, that would be 4 writes to map memory, and one write to attribute memory.

---

Here's a trick I find helpful. Create a shadow of the attribute table in RAM. That way, you have the full attribute table sitting in RAM, and can freely read and write it. When you update your shadow, you can also use that as your source data when updating the real attribute table. It only takes up 64 bytes of RAM (per nametable), totally worth it to make attribute manipulation easier.

The really easy algorithm for updating the attribute table (C-like psuedocode, not asm)
input:
X = x coordinate (in 16x16 tiles)
Y = y coordinate (in 16x16 tiles)
A = new attribute value (0-3)
tables:
mask_table: 00000011, 00001100, 00110000, 11000000 (in hex: 0x03, 0x0C, 0x30, 0xC0)
replicated_table: 00000000, 01010101, 10101010, 11111111 (in hex: 0x00, 0x55, 0xAA, 0xFF)

algorithm:
//"N" is what part of the attribute byte you are updating (0-3) (When making this asm, use an index register like X or Y to hold this)
N = (x & 1) + (y & 1) * 2
//"mask" is the bit value associated with that piece
mask = mask_table[N]
//"replicated" is the attribute value repeating four times
replicated = replicated_table[A]
//"offset" is the address within the attribute table
offset = x / 2 + y / 2 * 8
//(full ppu address = offset + 0x2000 + 0x400 * NAMETABLE_NUMBER + 0x3C0)
//Setting one new attribute within that byte:
T = attribute_ram[offset]
//bit clear the value of mask
T &= ~mask //bitwise compliment of mask (when making this asm, you can use a second table that's already XORed with FF instead)
//mask off the attribute, and set those bits
T |= replicated & mask
attribute_ram[offset] = T
Last edited by Dwedit on Sat Nov 30, 2024 11:08 am, edited 1 time in total.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
crystalmoon
Posts: 21
Joined: Wed Aug 23, 2023 12:45 am
Contact:

Re: troubles with updating the attributes correctly

Post by crystalmoon »

Dwedit wrote: Sat Nov 30, 2024 10:39 am Here's a trick I find helpful. Create a shadow of the attribute table in RAM. That way, you have the full attribute table sitting in RAM, and can freely read and write it. When you update your shadow, you can also use that as your source data when updating the real attribute table. It only takes up 64 bytes of RAM (per nametable), totally worth it to make attribute manipulation easier.

The really easy algorithm for updating the attribute table (C-like psuedocode, not asm)
input:
X = x coordinate (in 16x16 tiles)
Y = y coordinate (in 16x16 tiles)
A = new attribute value (0-3)
tables:
mask_table: 00000011, 00001100, 00110000, 11000000 (in hex: 0x03, 0x0C, 0x30, 0xC0)
replicated_table: 00000000, 01010101, 10101010, 11111111 (in hex: 0x00, 0x55, 0xAA, 0xFF)

algorithm:
//"N" is what part of the attribute byte you are updating (0-3) (When making this asm, use an index register like X or Y to hold this)
N = (x & 1) + (y & 1) * 2
//"mask" is the bit value associated with that piece
mask = mask_table[N]
//"replicated" is the attribute value repeating four times
replicated = replicated_table[A]
//"offset" is the address within the attribute table
offset = x / 2 + y / 2 * 8
//(full ppu address = offset + 0x2000 + 0x400 * NAMETABLE_NUMBER + 0x3C0)
//Setting one new attribute within that byte:
T = attribute_ram[offset]
//bit clear the value of mask
T &= ~mask //bitwise compliment of mask (when making this asm, you can use a second table that's already XORed with FF instead)
//mask off the attribute, and set those bits
T |= replicated & mask
attribute_ram[offset] = T
thank you so much for this! I'll try to implement it when I get back home soon, and will let you know of the results.
crystalmoon
Posts: 21
Joined: Wed Aug 23, 2023 12:45 am
Contact:

Re: troubles with updating the attributes correctly

Post by crystalmoon »

well, I've taken your approach, and the result is now significantly better. thank you very much! on my test map only the top row, left half seems to be wrong, but on the other map there are green streaks where there shouldn't be and a brown streak in the grass. still way better than my other attempts, though, but this suggests some logic error(/s).
rom_022.png
rom_022.png (4.11 KiB) Viewed 466 times
rom_023.png
rom_023.png (5.31 KiB) Viewed 466 times

Code: Select all

; src - tilemap source
DrawTilemap:
.scope
	NametableAddrLo		:= scratch1
	NametableAddrHi		:= scratch1+1
	AttributeAddrLo		:= scratch1+2
	AttributeReplic		:= scratch1+3
	AttributeCountr		:= scratch1+4
	AttributeX			:= scratch1+5
	AttributeY			:= scratch1+6
	AttributeBitmask	:= scratch1+7
	AttributeScratch	:= scratch_vacc

	lda #$20
	sta NametableAddrHi

	lda #$C0
	sta AttributeAddrLo ; set up attribute address pointer
	lda #$00
	sta NametableAddrLo ; set up nametable address pointer
	sta AttributeReplic ; set up attribute mockup counter
	sta AttributeX ; set up attribute x
	sta AttributeY ; set up attribute y
	ldx #64 ; loop, clearing attribute scratch ram
	tay
:
	sta attr_scratch,y
	iny
	dex
	bne :-

	ldy #$0 ; clear the registers
	tya
	tax
@loop:
	lda (src),y ; get the current metatile
	cmp #$FF ; is it $FF (end of list)?
	bne :+ ; if not, branch
	jmp @upload_to_vram ; end of subroutine
:
	tax ; use current metatile as index
	lda NametableAddrHi ; set up PPU address
	sta $2006
	lda NametableAddrLo
	sta $2006
	lda Metatiles1_TL,x ; get top left of the metatile
	sta $2007
	lda Metatiles1_TR,x ; get the top right of the metatile
	sta $2007
	lda NametableAddrHi ; add $20 to address
	sta $2006
	lda NametableAddrLo
	clc
	adc #$20
	sta $2006
	lda Metatiles1_BL,x ; get the bottom left of the metatile
	sta $2007
	lda Metatiles1_BR,x ; get the bottom right of the metatile
	sta $2007
	lda Metatiles1_Pal,x ; get the palette of the metatile... now the fun begins =|
 	pha ; back up the palette in the stack
	; -- BEGIN ATTRIBUTE CODE
	lda AttributeY ; load the attribute Y counter
	and #1 ; get the LSB
	rol a ; multiply by 2
	sta scratch_vacc ; store it to and it later
	lda AttributeX ; load the attribute X counter
	and #1 ; get the LSB
	ora scratch_vacc ; or with attribute Y
	tax ; transfer it to X
	lda AttributeBitmaskTable,x ; mask
	sta AttributeBitmask
	pla ; retrieve palette
	and #%11 ; make sure it is a valid palette
	tax ; use it as an index
	lda AttributeReplicationTable,x ; replication
	sta AttributeReplic ; back it up
	lda AttributeY ; begin offset calculation: x / 2 + 4y
	rol a
	rol a
	sta AttributeScratch
	lda AttributeX
	ror a
	clc
	adc AttributeScratch
	tax ; use offset as index
	lda attr_scratch,x ; load the value at that offset
	sta AttributeScratch ; back it up
	lda AttributeBitmask ; load the bitmask
	eor #$FF ; invert it
	and AttributeScratch ; and the current value with it
	sta AttributeScratch ; store this value, overwriting the previous one that we no longer need
	lda AttributeReplic ; load the replication value
	and AttributeBitmask ; and it with the bitmask
	ora AttributeScratch ; or it with the other value, getting our final result
	sta attr_scratch,x ; store it in the attribute table
	; -- END ATTRIBUTE CODE
	inc NametableAddrLo ; increment the low nametable addr by 2
	inc NametableAddrLo
	inc AttributeX ; increment the attribute X counter
; 	lda NametableAddrLo ; have we done 2 rows yet?
; 	and #$3F
; 	beq :+ ; if not, branch
; 	jsr @upload_to_vram ; upload the palette to VRAM
; :
	lda NametableAddrLo ; have we finished a row now?
	and #$1F
	bne @cont ; if not, branch
	lda NametableAddrLo ; add $20 to the nametable address to avoid drawing over the bottom row of tiles
	clc
	adc #$20
	sta NametableAddrLo
	lda #0 ; reset the attribute X counter
	sta AttributeX
	inc AttributeY ; increment the attribute Y counter
	bcc @cont ; if carry, increment the nametable high address
	inc NametableAddrHi

@cont:
	iny ; increment metatile pointer index
	beq @upload_to_vram ; if we've reached 255 tiles, return
	jmp @loop ; else, branch

@upload_to_vram:
	lda #$23 ; set up to load the attributes in the PPU
	sta $2006
	lda AttributeAddrLo
	sta $2006
	lda #64 ; load the counter for the attributes
	sta AttributeCountr
	ldx #0
@loop_:
	lda attr_scratch,x ; load the scratch byte
	sta $2007 ; write it to the PPU
	inx ; increase the index
	inc AttributeAddrLo ; increment the low attribute address
	lda AttributeAddrLo
	cmp #$C0 ; has it overflowed?
	bcs :+ ; if not, branch
	lda #$C0 ; reset the address back to $23C0
	sta AttributeAddrLo
:
	dec AttributeCountr ; decrement the counter
	bne @loop_ ; if it isn't the end yet, loop
	rts ; otherwise, return

AttributeBitmaskTable:
	.byte %00000011
	.byte %00001100
	.byte %00110000
	.byte %11000000

AttributeReplicationTable:
	.byte %00000000
	.byte %01010101
	.byte %10101010
	.byte %11111111

; AttributeBitshiftTable:
; 	.byte 5, 3, 1, 0

; 	lda AttributeY ; load the attribute Y counter
; 	and #1 ; get the LSB
; 	rol a ; multiply by 2
; 	sta scratch_vacc ; store it to and it later
; 	lda AttributeX ; load the attribute X counter
; 	and #1 ; get the LSB
; 	ora scratch_vacc ; or with attribute Y
; 	tax ; transfer it to X
; 	lda AttributeBitmaskTable,x ; load the correct bitmask
; 	sta AttributeBitmask ; store it in the scratch byte
; 	lda AttributeBitshiftTable,x ; load the correct bitshift amount
; 	sta scratch_vacc ; back it up
; 	lda AttributeX ; load the attribute X byte
; 	and #%1110 ; get rid of the LSB
; 	ror a ; shift it right
; 	tax ; use it as an index
; 	pla ; get the current metatile palette back
; 	stx AttributeMockup ; back up the index
; 	ldx scratch_vacc ; load the bitshift counter
; 	beq @skip_bitshifts ; if there's no bitshifts to do, branch
; :
; 	ror a ; bit shift it once
; 	dex ; is there more bitshifting to do?
; 	bne :- ; if not, branch
; @skip_bitshifts:
; 	ldx AttributeMockup ; reload index
; 	ora attr_scratch,x ; combine it with the correct attribute scratch byte
; 	sta attr_scratch,x

.endscope
would you like a ROM? and which map would you like the ROM to be on? (I tried to implement a map switching mechanism really quick, but I wasn't able to, unfortunately.)

edit: it seems as though some rows of updates are "delayed" for some reason. this seems to suggest my method of calculating the offset is wrong.
s11.png
s11.png (7.06 KiB) Viewed 453 times
hope this is readable for you.
User avatar
Dwedit
Posts: 5069
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: troubles with updating the attributes correctly

Post by Dwedit »

lda AttributeY ; load the attribute Y counter
and #1 ; get the LSB
rol a ; multiply by 2
ROL A is the wrong operation here, unless you know what the input carry flag is. Should use ASL (arithmetic shift left) instead.
(but carry was last updated by adding to a nametable address, so it should probably be still cleared, got lucky this time)
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
crystalmoon
Posts: 21
Joined: Wed Aug 23, 2023 12:45 am
Contact:

Re: troubles with updating the attributes correctly

Post by crystalmoon »

Dwedit wrote: Sat Nov 30, 2024 1:45 pm
lda AttributeY ; load the attribute Y counter
and #1 ; get the LSB
rol a ; multiply by 2
ROL A is the wrong operation here, unless you know what the input carry flag is. Should use ASL (arithmetic shift left) instead.
(but carry was last updated by adding to a nametable address, so it should probably be still cleared, got lucky this time)
for all the places I used ROL, I should use ASL?
and for when I used ROR?
User avatar
Dwedit
Posts: 5069
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: troubles with updating the attributes correctly

Post by Dwedit »

Also I wrote in the psuedocode "y / 2 * 8", which is not the same as "y * 4". "/" is the integer division operator that truncates. You could do y * 4, but you'll have to clear the lowest bit first.

And regarding Rotate vs Shift, you use Rotate when you have a particular carry flag that you want to rotate in, such as extending a shift to 16 bits. Otherwise you use shift.

ROL and ROR are rotations.
ASL and LSR are shifts. ASR is the signed-right-shift that's rarely used, unless you really need to sign-extend a number as you divide by 2.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
Post Reply