It is currently Fri May 25, 2018 11:36 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Tue Apr 26, 2016 9:51 am 
Offline

Joined: Tue Jul 01, 2014 4:02 pm
Posts: 285
This is slightly different than last topic, so I figured I'd start a new thread, even though it is related.

Essentially, I'd like to update attributes on 'metatile' level (2x2 tiles, or 1 quadrant of the attribute byte).

Thanks to the help here, I have been able to find the attribute block based on the tile in question. I am easily able to put in a hard value (say, #%00 01 10 11) to demo that this is working, and as expected.

What I'd like to be able to do is read the attribute byte, then ora in a value based on some math to find the quadrant of attribute byte.

Essentially, I've tried derivations of:

Code:
   LDA #%00000011
   STA temp
   LDA $2007
   ORA temp
   STA $2007


...where if #%00000000 was in $2007 originally, it should now read #%00000011.

But I know that both the read and the write are advancing the PPU address, correct?

What would be the best way to do this?

Thanks!


Top
 Profile  
 
PostPosted: Tue Apr 26, 2016 10:03 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10484
Location: Rio de Janeiro - Brazil
Modifying the attribute tables in place is going to be slow no matter how you do it, so that's only really an option if you have a good chunk of vblank time to spare.

One solution (which I use) is to buffer the attributes in RAM. Even if you need both attributes tables, 128 bytes isn't such a prohibitive amount of memory. You do all manipulations in RAM, and simply copy the final values to VRAM during vblank.

Another option is to re-calculate the new attribute byte(s) from scratch, using data from the surrounding blocks in the map, instead of manipulating the existing attribute bytes.

The ideal approach is to not need to manipulate attribute bytes at all, something that have with 32x32-pixel metatiles and no vertical scrolling are particularly good at.


Top
 Profile  
 
PostPosted: Tue Apr 26, 2016 10:27 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6293
Location: Canada
Doing it in place should look something like this:
Code:
bit $2002
ldx addr+1
stx $2006
ldy addr+0
sty $2006
lda $2007
lda $2007 ; reads from $2007 are buffered, you have to read twice because you get the second-last value
and attrib_mask ; bitmask with two 0s where your new attribute value is going to fit
ora attrib ; new attribute value, shifted into the hole made by the mask
;ldx addr+1
stx $2006
;ldy addr+0
sty $2006
sta $2007

This is a valid approach, though there are many other ways of going about this.


Top
 Profile  
 
PostPosted: Tue Apr 26, 2016 10:43 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10484
Location: Rio de Janeiro - Brazil
Yes, it's a valid approach, the only problem is indeed the speed. If you do this using completely unrolled code (a separate block like the one rainwarrior posted for each attribute you need to change), with all variables in ZP, each update will take 44 cycles. If you can fit that into your vblank budget, there's no problem in using this technique.


Top
 Profile  
 
PostPosted: Tue Apr 26, 2016 10:44 am 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7432
Location: Chexbres, VD, Switzerland
Quote:
Modifying the attribute tables in place is going to be slow no matter how you do it, so that's only really an option if you have a good chunk of vblank time to spare.

So long as only one or two attributes bytes are changed in a VBlank, I think this is a perfectly fine practice. Wasting $80 bytes to mirror attribute isn't a bad idea either - it's a matter of personal preference.


Top
 Profile  
 
PostPosted: Tue Apr 26, 2016 11:02 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 20071
Location: NE Indiana, USA (NTSC)
If you play sampled sound through the DMC, you need to treat VRAM as write-only while a sample is playing. If you try to perform a read-modify-write on VRAM while a sample is playing, the CPU will occasionally read extra bytes and ignore one or more, causing your data to come from the wrong VRAM address. LAN Master by Shiru reads the nametables (to save what's behind a dialog box that's about to appear), and if a drum sample plays at exactly the wrong time, the wrong data is read, leading to incorrectly restored data.

With respect to attribute tables, this leaves
  1. reconstructing attribute bytes from the level map,
  2. caching the attribute data in RAM, or
  3. always changing metatiles to other metatiles with the same attribute.
I think Super Mario Bros. does C: ? blocks change to used blocks, and brick blocks change to and from blank blocks. Most of my projects have done C as well, though I've toyed with A.


Top
 Profile  
 
PostPosted: Tue Apr 26, 2016 11:56 am 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7432
Location: Chexbres, VD, Switzerland
Tepples ideas a. and c. aren't bad either. In my game it's basically a huge mix of all those methods (*), and the code in regards to the attribute tables is so messy that each time I touch it it won't function any more. It is a miracle this huge mess works at all.

(*) I try to keep attribute 32x32 metatiles aligned with the NES attribute table, but this works only half of the time, because after the first 7.5 metatiles are drawn, there is misalignement, and after 14, it resets back to 0. So my engine can draw metatiles to positions 0-14 vertically, and half of them do two attribute table RMW operations, while the other half just writes the data straight. When the position 7 is used, it's even different because it has to do one plain write and one RMW operation. So far it's pretty clean and all.

Now, when I write something else than map data, such as the status bar or textboxes, I have another engine which reconstruct colour data from map data, and somehow combines them with a constant colour. This is the part which is really messy, and where it's a miracle it works at all. It's a very large chunk of messy assembly code. Unfortunately there's no way around this.


Top
 Profile  
 
PostPosted: Mon May 02, 2016 7:35 am 
Offline

Joined: Tue Jul 01, 2014 4:02 pm
Posts: 285
I know I'm incredibly close with this routine. Again, this is all happening during off-render time, so speed is not an issue. Here's what I've conceived, and here's the spot I can't seem to get right:

Code:
GetAttAddress:
    LDA addr+1
    LSR
    LSR
    CMP #$20
    AND #%00000111
    STA temp

    LDA addr
    ROL
    ASL
    ASL
    ASL
    ORA #$C0
    ORA temp
    STA attributeToChangeAddress

    LDA addr
    ORA #$03
    STA attributeToChangeAddress+1


GetAttQuad:
    ;;;;;; [b]Trouble spot[/b]
    ;;;; What is the right way to get the quadrant of the attribute?
    ;; determination of top left, top right, bottom left, bottom right...

ifTopLeftAttr:
    LDA #%11111100
    STA attTemp
    LDA #%00000011
    STA attTemp+1
    JMP haveAttrToModify

;;etc.....routines for all four, ora/and the correct values

haveAttrToModify:

    bit $2002
    LDA attributeAddressToChange+1
    STA $2006
    LDA attributeAddressToChange
    STA $2006
    LDA $2007
    LDA $2007
    AND attTemp
    ORA attTemp+1
    STA temp
   
    LDA attributeAddressToChange+1
    STA $2006
    LDA attributeAddressToChange
    STA $2006
    LDA temp
    STA $2007




Does this generally look sound? I've tried to synthesize a lot of your collective advice in putting it together, but haven't quite figured out how to get the attribute quadrant to modify...or possibly I have, but something else in this routine is funky causing me to get false results.

Thanks for having a look and the patience!


Top
 Profile  
 
PostPosted: Mon May 02, 2016 11:00 am 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7432
Location: Chexbres, VD, Switzerland
Why don't you use X and Y to make this piece of code shorter/simpler? For instance :
Code:
    bit $2002
    LDX attributeAddressToChange+1
    STX $2006
    LDY attributeAddressToChange
    STY $2006
    LDA $2007
    LDA $2007
    AND attTemp
    ORA attTemp+1
   
    STX $2006
    STY $2006
    STA $2007


Top
 Profile  
 
PostPosted: Mon May 02, 2016 11:08 am 
Offline

Joined: Tue Jul 01, 2014 4:02 pm
Posts: 285
Could, but using x and y in outer loop function...would have to store them to temp, restore them at end, continue the loop, or otherwise push them and restore them using the stack. This just seemed a little easier in context.


Top
 Profile  
 
PostPosted: Mon May 02, 2016 11:47 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2053
Location: DIGDUG
Your code won't work. Scrap all of it. I'll respond in more detail when I have more time (unless someone else beats me to it).

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Mon May 02, 2016 2:22 pm 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2053
Location: DIGDUG
I'm away from my computer, so all this is untested, I worked it out on scratch paper during lunch at work...

Ok, converting a nametable address to its attribute address, so let's look at the first attribute byte...

23c0 covers Nametable addresses...

2000-1, 2002-3
2020-1, 2022-3
2040-1, 2042-3
2060-1, 2062-3

The next attribute byte, 23c1, is the same except the $04 bit is set in addresses. Starting 2004-5, etc.

We need 3 bit from X coordinates, and 3 from Y coordinates.
Therefore, the low 3 bits of attribute table correspond to these bits of the low nametable address...
---1 11--

The next attribute table byte down from the top left is 23c8, is covers these nametable addresses...

2080-1, 2042-3
20a0-1, 20a2-3
20c0-1, 20c2-3
20e0-1, 20e2-3

You see the $80 bit of the low nametable address byte is the next bit we need. We also need two more, the lowest 2 bits from the high nametable byte.

1--- ---- low
---- --11 high

Heres some untested code...
(Note addr = low byte of address, addr+1 = high byte)

Code:
Lda addr
And #$1c ;mask 3 bits
Lsr a
Lsr a
;;;;Lsr a ;too many
Sta temp

Lda addr
And #$80 ; mask 1 bit
Asl a ;put it in carry flag
Lda addr+1
And #03 ;mask 2 bits
Rol a ;shift bit in from carry
Asl a
Asl a
Asl a
Clc
Adc temp
Ora #$c0
Sta attrib_addr

Bit $2002
Lda addr+1
Ora #03 ;high byte of attrib table
Sta $2006
Lda attrib_addr
Sta $2006
Lda $2007 ;junk
Lda $2007 ;data
Sta temp

;now decide which 2 bits to modify
;the $02 bit of low nametable address decides L/R
;the $40 bit of low nametable address decides Top/Bot
;bits are aligned 44332211 =
;1, 2
;3, 4

Lda addr
And #$40 ;top or bottom?
BNE BOTTOM

TOP:
Lda addr
And #02
BNE TOP_R

TOP_L:
Lda temp ;our data
And #$fc ; mask out bits
Sta temp
Lda new_bits ;in right 2 bit position
Ora temp
Sta temp
Jmp END

TOP_R:
Lda temp ;our data
And #$f3 ; mask out bits
Sta temp
Lda new_bits ;in right 2 bit position
Asl a
Asl a
Ora temp
Sta temp
Jmp END

BOTTOM:
Lda addr
And #02
BNE BOT_R

BOT_L:
Lda temp ;our data
And #$cf ; mask out bits
Sta temp
Lda new_bits ;in right 2 bit position
Asl a
Asl a
Asl a
Asl a
Ora temp
Sta temp
Jmp END

BOT_R:
Lda temp ;our data
And #$3f ; mask out bits
Sta temp
Lda new_bits ;in right 2 bit position
Clc
Ror a
Ror a
Ror a
Ora temp
Sta temp
;Jmp END

END: ;put new data in attrib table
Lda addr+1
Ora #03
Sta $2006
Lda attrib_addr
Sta $2006
Lda temp
Sta $2007



I think that's right. I'll check it when I get home.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Last edited by dougeff on Mon May 02, 2016 3:02 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Mon May 02, 2016 2:50 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6293
Location: Canada
I think you have one extra LSR at the top there (we want X / 4, not X / 8, right?) Other than that error, I think your code for calculating the address is functionally the same as OP's, but a little less efficient?

Kinda curious about the capitalization style. Do you write assembly that way, capitalizing the first letter of every mnemonic? I've never seen anyone do this before.


Top
 Profile  
 
PostPosted: Mon May 02, 2016 3:07 pm 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2053
Location: DIGDUG
Quote:
little less efficient


OPs code doesn't work / is incomplete.

Quote:
capitalization


No. I'm typing quickly in my phone.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Mon May 02, 2016 3:09 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6293
Location: Canada
dougeff wrote:
Quote:
little less efficient

OPs code doesn't work / is incomplete.

I was referring to just the part that calculates the address. Your version has an error, and OPs is equivalent but smaller. Did you see something wrong with that section of his code? (GetAttAddress)

The missing part was just selecting a quadrant, the rest of OPs code looked fine to me. What about it do you think was broken, or necessitated a complete rewrite?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 5 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group