Where to find OAM table?

Discussion of hardware and software development for Super NES and Super Famicom.

Moderator: Moderators

Forum rules
  • For making cartridges of your Super NES games, see Reproduction.
Post Reply
Esns68
Posts: 31
Joined: Tue Jan 07, 2020 2:03 pm

Where to find OAM table?

Post by Esns68 » Wed Feb 03, 2021 6:30 am

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.

tepples
Posts: 22280
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Where to find OAM table?

Post by tepples » Wed Feb 03, 2021 6:40 am

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.

Pokun
Posts: 1746
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Where to find OAM table?

Post by Pokun » Wed Feb 03, 2021 3:35 pm

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.

Esns68
Posts: 31
Joined: Tue Jan 07, 2020 2:03 pm

Re: Where to find OAM table?

Post by Esns68 » Thu Feb 04, 2021 6:52 am

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"
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.
So this would be the "OAM layout" that they're talking about?

Pokun
Posts: 1746
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Where to find OAM table?

Post by Pokun » Thu Feb 04, 2021 8:29 am

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):

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

Esns68
Posts: 31
Joined: Tue Jan 07, 2020 2:03 pm

Re: Where to find OAM table?

Post by Esns68 » Sat Feb 06, 2021 5:39 am

Thank you.

By the way, what does the "+" sign do in all the things like "Sprite+1"?

User avatar
dougeff
Posts: 2817
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: Where to find OAM table?

Post by dougeff » Sat Feb 06, 2021 5:59 am

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
nesdoug.com -- blog/tutorial on programming for the NES

Esns68
Posts: 31
Joined: Tue Jan 07, 2020 2:03 pm

Re: Where to find OAM table?

Post by Esns68 » Sun Feb 07, 2021 4:08 am

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.

tepples
Posts: 22280
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Where to find OAM table?

Post by tepples » Sun Feb 07, 2021 12:01 pm

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.

Pokun
Posts: 1746
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Where to find OAM table?

Post by Pokun » Sun Feb 07, 2021 3:21 pm

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:

Code: Select all

  ldx #$0000
  stx $2102           ;set OAM address: low table address 0
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.



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?
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).

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)
Sprite 1 will have the following addresses:

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)
...and so on.

Post Reply