Where to find OAM table?
Moderator: Moderators
Forum rules
- For making cartridges of your Super NES games, see Reproduction.
Where to find OAM table?
Title says all. I put them both into one since they're quick questions.
I hear a lot about the importance of seeing the OAM table for the SNES. Any one know where you can find it?
I almost found one in another post but the link was deleted.
I hear a lot about the importance of seeing the OAM table for the SNES. Any one know where you can find it?
I almost found one in another post but the link was deleted.
Re: Where to find OAM table?
OAM is in the S-PPU, behind B Bus ports $2102-$2103 (address), $2104 (write data), and $2138 (read data). Usually it's copied from work RAM (CPU memory) using DMA.
If you're looking for where in work RAM a particular game keeps its OAM table, look for DMA with $04 (that is, the low byte of $2104) as the B bus address. This means look for $04 written to $4301 (DMA channel 0 B bus address), $4311 (same for channel 1), $4321 (same for channel 2), ..., or $4371 (same for channel 7). Then once you find it, look for the value written to $4302-$4304 (or $4312-$4314, or $4322-$4324, etc.), which controls the corresponding DMA channel's source address. The value written to $4305-$4306 (or $4315-$4316, or $4325-$4326, etc.) controls the length of the copy. If the length value is $0200 (512), it represents the length of the main OAM table. If the length value is $0220 (544), it represents the length of the main OAM table plus the high X and size table.
If you're looking for where in work RAM a particular game keeps its OAM table, look for DMA with $04 (that is, the low byte of $2104) as the B bus address. This means look for $04 written to $4301 (DMA channel 0 B bus address), $4311 (same for channel 1), $4321 (same for channel 2), ..., or $4371 (same for channel 7). Then once you find it, look for the value written to $4302-$4304 (or $4312-$4314, or $4322-$4324, etc.), which controls the corresponding DMA channel's source address. The value written to $4305-$4306 (or $4315-$4316, or $4325-$4326, etc.) controls the length of the copy. If the length value is $0200 (512), it represents the length of the main OAM table. If the length value is $0220 (544), it represents the length of the main OAM table plus the high X and size table.
Re: Where to find OAM table?
I don't fully understand the question but, Mesen-S has an OAM viewer which is very useful. There is also the memory viewer which is like a hex editor and useful to see the raw memory values instead of the more visual viewer. Just beware that it always shows the byte address of everything, including VRAM (which is normally described as word addresses). You must divide it with 2 when viewing VRAM to get the proper VRAM word address. I keep stepping on this rake.
Re: Where to find OAM table?
So I found one of the post that mentioned it and it said
"I suggest studying the OAM layout - you can find it here: *Dead link"
"I suggest studying the OAM layout - you can find it here: *Dead link"
So this would be the "OAM layout" that they're talking about?tepples wrote: ↑Wed Feb 03, 2021 6:40 am OAM is in the S-PPU, behind $2102-$2103 (address), $2104 (write data), and $2138 (read data).
Usually it's copied from work RAM (CPU memory) using DMA. So if you're looking for where in work RAM a particular game keeps its OAM table, look for DMA with $04 (that is, the low byte of $2104) as the B bus address. This means look for $04 written to $4301 (DMA channel 0 B bus address), $4311 (same for channel 1), $4321 (same for channel 2), ..., or $4371 (same for channel 7). Then once you find it, look for the value written to $4302-$4304 (or $4312-$4314, or $4322-$4324, etc.), which controls the corresponding DMA channel's source address. The value written to $4305-$4306 (or $4315-$4316, or $4325-$4326, etc.) controls the length of the copy. If the length value is $0200 (512), it represents the length of the main OAM table. If the length value is $0220 (544), it represents the length of the main OAM table plus the high X and size table.
Re: Where to find OAM table?
The OAM is described here and here. Basically it's a piece of memory, 544 byte in size (divided in two tables 512 and 32 byte respectively), that holds sprite attributes for all 128 sprites that the PPU draws. Attributes includes horizontal and vertical positions of each sprite and other information the PPU needs to know where and how to draw the sprite on the TV. Like Tepples said, you can't access it directly (like you can do with WRAM), but you have to use $2102, $2103 and $2104 to write to it (and thus move around the sprites accordingly), and only in vblank or forced blanking.
Example code (64tass syntax):
Here I first made two variables for the player's position (X and Y) in WRAM. I also have an OAM buffer in WRAM starting at $0100 (544 byte large). I just used the = symbol to assign the WRAM address directly for simplicity's sake. You might have a better way to declare variables in your assembler.
In the main thread I don't move the sprite directly but instead manipulate the two variables representing the player's position. Increments moves downwards and rightwards respectively, while decrement moves up and left. Since I keep using INC, the player will move diagonally down-right.
At the end of the main thread I update my OAM buffer with the variables and waits for the next NMI. I want to use sprite 0 to represent the player, so I must update X and Y of sprite 0 to the same values that the player's X and Y are. These are the first two bytes in OAM (oam_buffer+0 and oam_buffer+1 in my OAM buffer). In NMI I use DMA to update the real OAM with the content of my OAM buffer. This is necessary as OAM can only be accessed in vblank.
The <> and ` are required in 64tass to make the address 16-bit and for getting the bank byte of the address respectively. Your assembler might use different symbols for this.
Example code (64tass syntax):
Code: Select all
p1_x = $0020 ;variable for the player's X-position
p1_y = $0021 ;variable for the player's Y-position
oam_buffer = $0100 ;OAM buffer in WRAM, 544 byte
main:
move:
inc p1_x ;move player downwards
inc p1_y ;move player to the right
update_sprite:
lda p1_x
sta oam_buffer+0 ;store player's X in sprite 0's X-position attribute
lda p1_y
sta oam_buffer+1 ;store player's Y in sprite 0's Y-position attribute
nmi_wait:
lda nmiend_flag
beq nmi_wait ;wait for NMI, limits logic to a fixed frame rate
stz nmiend_flag ;clear NMI completion flag
jmp main
NMI:
sep #$20
.as
rep #$10
.xl ;set A to 8-bit mode, X and Y to 16-bit mode
obj_update: ;update OAM using the OAM buffer in WRAM (every NMI)
ldx #$0000
stx $2102 ;set OAM address: low table address 0
lda #$02
sta $4300 ;DMA 0: CPU +1 -> PPU 2 byte 1 reg 2 writes
lda #$04
sta $4301 ;destination: $2104 (OAM data)
ldx #<>(oam_buffer+0)
stx $4302
lda #`(oam_buffer+0)
sta $4304 ;source: OAM buffer in WRAM
ldx #$0220
stx $4305 ;data length: 512+32 byte
lda #$01
sta $420B ;trigger general-purpose DMA channel 0
lda #$01
sta nmiend_flag ;indicate that NMI is complete
rti
In the main thread I don't move the sprite directly but instead manipulate the two variables representing the player's position. Increments moves downwards and rightwards respectively, while decrement moves up and left. Since I keep using INC, the player will move diagonally down-right.
At the end of the main thread I update my OAM buffer with the variables and waits for the next NMI. I want to use sprite 0 to represent the player, so I must update X and Y of sprite 0 to the same values that the player's X and Y are. These are the first two bytes in OAM (oam_buffer+0 and oam_buffer+1 in my OAM buffer). In NMI I use DMA to update the real OAM with the content of my OAM buffer. This is necessary as OAM can only be accessed in vblank.
The <> and ` are required in 64tass to make the address 16-bit and for getting the bank byte of the address respectively. Your assembler might use different symbols for this.
Re: Where to find OAM table?
Thank you.
By the way, what does the "+" sign do in all the things like "Sprite+1"?
By the way, what does the "+" sign do in all the things like "Sprite+1"?
Re: Where to find OAM table?
the assembler will add the label to the number, before generating the binary code.
if oam_buffer is at $1000
STA oam_buffer+1
is the same as STA $1001
if oam_buffer is at $1000
STA oam_buffer+1
is the same as STA $1001
nesdoug.com -- blog/tutorial on programming for the NES
Re: Where to find OAM table?
I'm starting catch it a little, still not all way there.
Just making sure, when you DMA load the graphics to the data, are you supposed to load the whole amount of space as the entire OAM,
no matter if it's all filled with sprite loads or only a few.
Or do you just the enter the total amount of bytes of all the graphics you loaded?
Or, do you have to load each graphic load separately?
Depending on the answer to that, how do you transfer things to the certain addresses of the OAM?
Or does the OAM have it's own addresses?
This is another thing that is confusing to me. I notice in some codes you have to write to certain address of where you loaded the sprite, like the
x and y at the beginning. And other things at a later addresses, the first sprite for example at around $0100 and $0200. Don't what that is, but would basically have to a count the way to a $100 above address from the start of each sprite's address?
I apologize if any of this was already answered by you all, I just want to make sure I'm comprehending you right.
And this will help me understand it more.
Just making sure, when you DMA load the graphics to the data, are you supposed to load the whole amount of space as the entire OAM,
no matter if it's all filled with sprite loads or only a few.
Or do you just the enter the total amount of bytes of all the graphics you loaded?
Or, do you have to load each graphic load separately?
Depending on the answer to that, how do you transfer things to the certain addresses of the OAM?
Or does the OAM have it's own addresses?
This is another thing that is confusing to me. I notice in some codes you have to write to certain address of where you loaded the sprite, like the
x and y at the beginning. And other things at a later addresses, the first sprite for example at around $0100 and $0200. Don't what that is, but would basically have to a count the way to a $100 above address from the start of each sprite's address?
I apologize if any of this was already answered by you all, I just want to make sure I'm comprehending you right.
And this will help me understand it more.
Re: Where to find OAM table?
If you load a particular sprite position into OAM on the S-PPU,* it will continue to display that entry as a sprite as long as rendering is enabled until you overwrite that entry. If you know you won't be using a lot of OAM space, you don't have to copy those entries every frame so long as you've previously cleared them.
To transfer data to OAM, write the address to $2102-$2103 and then write data to $2104 as described in Fullsnes. This is most commonly done with DMA, not individual programmed writes. Tile numbers in OAM refer to VRAM and are measured in 16-word (32-byte) units from the base of sprite memory set in OBSEL ($2101). See Fullsnes for details.
* This differs from the NES, where you practically have to copy the entirety of the sprite list to OAM every frame because of how DMA works and because of serious bugs in the 60 Hz PPU's OAM DRAM controller that are exposed during partial filling.
To transfer data to OAM, write the address to $2102-$2103 and then write data to $2104 as described in Fullsnes. This is most commonly done with DMA, not individual programmed writes. Tile numbers in OAM refer to VRAM and are measured in 16-word (32-byte) units from the base of sprite memory set in OBSEL ($2101). See Fullsnes for details.
* This differs from the NES, where you practically have to copy the entirety of the sprite list to OAM every frame because of how DMA works and because of serious bugs in the 60 Hz PPU's OAM DRAM controller that are exposed during partial filling.
Re: Where to find OAM table?
The devdocs actually recommends to update the whole OAM every frame, no matter how many sprites you actually use, to use it as a way to clock controller auto-reading. By having the OAM-DMA in the beginning of NMI the controller auto-reading (which also starts at the beginning of vblank) has time to finish before you read the result the next frame. I also update the whole OAM out of habit because that's how it was done on the NES which I'm used to. I like that you can just put the OAM-DMA routine at the start of the NMI and then forget about it. Any changes you do to the OAM buffer will always be reflected in sprites on the screen the next frame.
You just need to make sure you hide all sprites in the OAM buffer you don't use though. This is done by giving every sprite in oam_buffer the Y-value $F0 or the X-value $101 for example. The Y=$F0 doesn't hide too large sprites (they will stick out on the top of screen) while X=$101 hides any sized sprites, but the latter also requires you to write the high X-bit in the high OAM table of the OAM buffer. Although X=$100 also hides the sprite, it's bad as it makes the sprite count toward the sprites/scanline limit, which X=$101 doesn't do.
Initialize the OAM buffer with one of these values for all unused sprites before you turn on the screen.
As you can see in my example code, I first set the OAM address to 0 with this code:
X is 16-bit here so it writes $00 to both $2102 and $2103. Further down I use "lda #$04" and "sta $4301" which is done to set DMA destination to $2104. This makes it blast all sprites (512+32 byte) from "oam_buffer" into OAM via $2104 using the DMA hardware.
In my above code example the sprite 0 has the following addresses in the OAM buffer:
Sprite 1 will have the following addresses:
...and so on.
You just need to make sure you hide all sprites in the OAM buffer you don't use though. This is done by giving every sprite in oam_buffer the Y-value $F0 or the X-value $101 for example. The Y=$F0 doesn't hide too large sprites (they will stick out on the top of screen) while X=$101 hides any sized sprites, but the latter also requires you to write the high X-bit in the high OAM table of the OAM buffer. Although X=$100 also hides the sprite, it's bad as it makes the sprite count toward the sprites/scanline limit, which X=$101 doesn't do.
Initialize the OAM buffer with one of these values for all unused sprites before you turn on the screen.
As you can see in my example code, I first set the OAM address to 0 with this code:
Code: Select all
ldx #$0000
stx $2102 ;set OAM address: low table address 0
Did you understand that OAM (544 byte) is divided in a low table (512 byte) and a high table (32 byte)? If not, read Fullsnes or the OAM entry at superfamicom.org wiki again. Every sprite uses 4 bytes in the low table and 2 bits in the high table for its sprite attribute data. Since I put my oam_data at $0100 ($0100 to $0220) the high table will start at $0200 (512 bytes after the start address of the buffer).This is another thing that is confusing to me. I notice in some codes you have to write to certain address of where you loaded the sprite, like the x and y at the beginning. And other things at a later addresses, the first sprite for example at around $0100 and $0200. Don't what that is, but would basically have to a count the way to a $100 above address from the start of each sprite's address?
In my above code example the sprite 0 has the following addresses in the OAM buffer:
Code: Select all
X-position oam_buffer+0
Y-position oam_buffer+1
CHR number oam_buffer+2
Misc attributes oam_buffer+3
High-X and sprite size oam_buffer+512 (bit 0 and 1)
Code: Select all
X-position oam_buffer+4
Y-position oam_buffer+5
CHR number oam_buffer+6
Misc attributes oam_buffer+7
High-X and sprite size oam_buffer+512 (bit 2 and 3)
Re: Where to find OAM table?
I do not want to set up a separate topic, so I will propose a question here - I would like to make sure:
It is about the second OAM table (32 bytes) - if sprites of maximum 16x16 size (not larger) will be used, and moving on the X axis will be only on one, static 256x224 screen, and any sprite will be thrown out of the screen by writing F0 to Y , - will this second oam table contain only zeros?
It's mainly about the state of the High-X bit because I'm not sure here - the bit OFF means that the sprite moves in the 00-FF range on the X axis exactly the same as on the NES (visible on the screen)? On the other hand, the SET bit causes the sprite to move OUT of the screen on negative values for the X axis?
It is about the second OAM table (32 bytes) - if sprites of maximum 16x16 size (not larger) will be used, and moving on the X axis will be only on one, static 256x224 screen, and any sprite will be thrown out of the screen by writing F0 to Y , - will this second oam table contain only zeros?
It's mainly about the state of the High-X bit because I'm not sure here - the bit OFF means that the sprite moves in the 00-FF range on the X axis exactly the same as on the NES (visible on the screen)? On the other hand, the SET bit causes the sprite to move OUT of the screen on negative values for the X axis?
Re: Where to find OAM table?
X is a 9-bit signed value. With the high X bit off, you have values 0-255, where the left side is within the screen. With the high X bit on, you have values -256 through -1. Values -15 through -1 represent the sprite partially off the left side of the screen. (Values -256 through -16 don't do you much good, as they still count against the 32 sprites on a line. It's better to just hide such sprites with Y coordinate $F0.)
If you know all sprites will be 16x16 and none will be hanging off the screen, you can set the size bits in OBSEL to 16x16 and 32x32 and use a second OAM table of all zeroes.
If you know all sprites will be 16x16 and none will be hanging off the screen, you can set the size bits in OBSEL to 16x16 and 32x32 and use a second OAM table of all zeroes.
Re: Where to find OAM table?
This video here is very good at explaining these numbers visually.
BTW I thought a sprite with X=257 would not count toward the scanline limit? It says so in the dev docs.
BTW I thought a sprite with X=257 would not count toward the scanline limit? It says so in the dev docs.
- jeffythedragonslayer
- Posts: 344
- Joined: Thu Dec 09, 2021 12:29 pm
Re: Where to find OAM table?
I created a table of the OAM layout here: https://snes.nesdev.org/wiki/OAM_layout
- rainwarrior
- Posts: 8734
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: Where to find OAM table?
My understanding is that X=256 exactly has a bug that causes it to count against the scanline, but X>256 is fully capable of removing a sprite from the scanline limit. So X=257 should do it, yes.