Palette Checker

Discussion of hardware and software development for Super NES and Super Famicom. See the SNESdev wiki for more information.

Moderator: Moderators

Forum rules
  • For making cartridges of your Super NES games, see Reproduction.
User avatar
Drew Sebastino
Formerly Espozo
Posts: 3496
Joined: Mon Sep 15, 2014 4:35 pm
Location: Richmond, Virginia

Palette Checker

Post by Drew Sebastino »

Well, I've been trying to make a code that looks for an object's palette bits based on what each palette is. I've made a 64 byte "palette table" in ram where the first 32 bytes contain individual numbers that correspond to different palettes and where the second 32 bytes say how many objects are currently using the palette. In the code, it first looks at the first 32 bytes to see if any of the existing palettes match the one you are trying to add, and if none of them do, you go to the last 32 bytes that if 0, you can create a new palette in the area in the first 32 bytes that corresponds to the last 32 bytes and also add 1 to the spot you found the empty palette slot in in the last 32 bytes to say that the slot is being occupied. I think I commented pretty good, but If you're unsure about something or just don't get it, you can ask me to better explain. There are a few thing's I'm not sure about that I commented on, and if you have a really good idea for a better way to check for palettes, you can also say that.

This code is for ca65, but I'm sure you can understand it if you don't have it.

Code: Select all

.proc start_palette_checker
  rep #$30    ; A=16, X/Y=16
  ldx #$0000

palette_checker_loop1:
  lda PaletteTable,x			;first 32 bytes specify what palette is in the palette slot
  cmp PaletteRequest			;sees if the color in the palette slot matches with the one you are trying to add
  bne prepare_for_palette_checker_loop

done:
  lda PaletteTable+32,x		;the last 32 bytes say how many objects are using that palette
  inc				;so if it is 0, it is safe to overwrite it. a new object is using that palette,
  sta PaletteTable+32,x		;so increase it by 1.

  sep #$20

  ldx
  ora Attributes+1	;this will be direct paged for object slots, I just forgot how to do it without having to do a:.
  sta Attributes+1	;Attributes is the exact same thing as bytes 3 and 4 in oam.
  rts

prepare_for_palette_checker_loop:
  inx					;we're using 16 bits, so you increase x by 2.
  inx
  cpx #$0020				;sees if we're at the second half of the palette table.
  bcc palette_checker_loop1

palette_checker_loop2:
  lda PaletteTable,x
  bne prepare_for_palette_checker_loop	;0 means that no objects are using the palette slot,
					;so it is safe to overwrite it.

done2:
  ldx
  ror			;same thing as dividing by 2? How is asl any different than rol then?
  ora Attributes+1	;this will be direct paged for object slots.
  sta Attributes+1	;Attributes is the exact same thing as bytes 3 and 4 in oam.

  lda PaletteRequest
  sta PaletteTable-32,x		;remember, x is twice as much here as it would be in done1 (64/2 = 32)
  lda PaletteTable,x		;the last 32 bytes say how many objects are using that palette
  inc				;so if it is 0, it is safe to overwrite it. a new object is using that palette,
  sta PaletteTable,x		;so increase it by 1.

  sep #$20	;how do you get an 8 bit x, because I'm pretty sure that's what you need here
  ldx		;(by the way, this next section is going to be pretty rough, because I've never directly worked with DMA...)
  asl		;since a palette is 32 bytes and we already have the palette number times 4,
  asl		;we multiply it by 8 for the result.
  asl
  asl
  stx $2121	;says which color to start at

  lda PaletteRequest
  sta $4302	; Store data offset into DMA source offset (PaletteRequest is really an adress,
  lda #$01	; and because I'm only using 1 bank for palettes, every number can be its own adress)
  sta $4304	; Store data bank into DMA source bank (bank 1)
  lda #$10
  sta $4305	; Store size of data block (32 bytes = 1 16 color palette)

  stz $4300	; Set DMA Mode (byte, normal increment)
  lda #$22	; Set destination register ($2122 - CGRAM Write)
  sta $4301
  lda #$01	; Initiate DMA transfer
  sta $420B
  rts

.endproc
User avatar
Drew Sebastino
Formerly Espozo
Posts: 3496
Joined: Mon Sep 15, 2014 4:35 pm
Location: Richmond, Virginia

Re: Palette Checker

Post by Drew Sebastino »

You know, I just realized something. If I have about 20 bullets that are the exact same (including color) onscreen, then you just lost a good deal of time doing the exact same thing over again. Do you think you could have it to where a table of registers hold a value for how many objects are using that palette, and where the palette is in the palette table? You would have something like 2 registers (4 in 16 bit) for an explosion color, where the first says how many objects are using the palette, and another that has where the palette is in the palette table. If the first register is 0 and you are adding a new object that uses that palette, you look at the palette table for a an open spot. the 2nd register in the explosion palette registers would be set exactly like the palette bits in oam, (These: %00001110) so you only need to convert the number once.

Does this make any sense? How does everyone else switch palettes?

(I'll try to make the code soon.)
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Palette Checker

Post by rainwarrior »

Well, this is treating it like a very generic problem, but every game has a different use case for palettes. Maybe a specific solution would work better?

You could write special code for drawing bullets that fetches the palette from your generic system once, then reuses that for all bullets.

You could also just reserve specific palettes for specific objects (at specific times).

There's a million ways to solve the problem. What works best depends entirely on the situation.
psycopathicteen
Posts: 3140
Joined: Wed May 19, 2010 6:12 pm

Re: Palette Checker

Post by psycopathicteen »

This is going to be a last resort right? Doing dynamic animation on individual sprites, and organizing them into screen/vram regions, while making sure the metasprite animates as a whole while keeping track of dma usage, and having the ability to sync up explosion sprites, except when it can't due to screen/vram regions is complicated enough without adding color palettes to the mix.
User avatar
Khaz
Posts: 314
Joined: Thu Dec 25, 2014 10:26 pm
Location: Canada

Re: Palette Checker

Post by Khaz »

I would personally think most games would favor a mixed approach - reserve a few palettes for objects that will be on screen always or frequently (like bullets), and then swap out the rest as needed. That or plan ahead through smart level design to just not overflow your palette space by segregating certain types of enemies, so you can just load the whole block of palettes and update it as needed when you approach different sets of enemies

My gut feeling is this approach may be a bit more processing than it's worth at the SNES level.
User avatar
Drew Sebastino
Formerly Espozo
Posts: 3496
Joined: Mon Sep 15, 2014 4:35 pm
Location: Richmond, Virginia

Re: Palette Checker

Post by Drew Sebastino »

psycopathicteen wrote:This is going to be a last resort right?
It wasn't going to be. :oops:
psycopathicteen wrote:and organizing them into screen/vram regions
I'm actually not going to do that, but I thought it would have been a cool idea, but yeah, maybe I'm being a bit over ambitious. I haven't run into slowdown yet, but I really haven't done much of anything. I wonder how much I should be worrying... (I'm most definitely going to use fast rom, but I don't know how much it would help.)
Khaz wrote:I would personally think most games would favor a mixed approach - reserve a few palettes for objects that will be on screen always or frequently
Yeah, that's true. I think I'll reserve palettes for player 1 and 2, so at least that's a little less to worry about.

Does anyone know how DKC handles changing palettes? It seems pretty accurate and it definitely isn't hardcoded to switch palettes halfway through the level or anything. One weird thing about how DKC handles palettes is that if there are too many objects with unique palettes on screen, the game will sometimes mess up the hud and banana palette for an enemy, and it will stay messed up even when all the objects disappear. I would have thought they would make it to where it checks if all the palettes are used up and just doesn't write anything then, (meaning it doesn't write over palette 1) but I guess not.
User avatar
Khaz
Posts: 314
Joined: Thu Dec 25, 2014 10:26 pm
Location: Canada

Re: Palette Checker

Post by Khaz »

Espozo wrote:Does anyone know how DKC handles changing palettes? It seems pretty accurate and it definitely isn't hardcoded to switch palettes halfway through the level or anything. One weird thing about how DKC handles palettes is that if there are too many objects with unique palettes on screen, the game will sometimes mess up the hud and banana palette for an enemy, and it will stay messed up even when all the objects disappear. I would have thought they would make it to where it checks if all the palettes are used up and just doesn't write anything then, (meaning it doesn't write over palette 1) but I guess not.
I too would be thrilled to hear more about how official games handled this particular issue.
psycopathicteen
Posts: 3140
Joined: Wed May 19, 2010 6:12 pm

Re: Palette Checker

Post by psycopathicteen »

Espozo wrote:
psycopathicteen wrote:and organizing them into screen/vram regions
I'm actually not going to do that, but I thought it would have been a cool idea, but yeah, maybe I'm being a bit over ambitious. I haven't run into slowdown yet, but I really haven't done much of anything. I wonder how much I should be worrying... (I'm most definitely going to use fast rom, but I don't know how much it would help.)
That simplifies it a lot. Metasprites would only need to be updated as a whole, instead of checking for each and every sprite. The screen/vram region thing sounds like it could be taxing on the CPU, I don't know how much though. I would only go that route if I absolutely need to.

The thing about the color palette doesn't sound CPU taxing at all. There are only 8 palettes to look at a time.

Some things to fix in your code:
- the whole thing can just be done in 8-bit mode, since your dealing with just 8-bit numbers
- there are 8 palettes, not 32 palettes
- replace "ldx" with "txa"
- palette value starts at bit-9 of the attributes word, so you need an "asl" before storing to "attributes+1"
- replace "ror" with "and #$03"
- DMA should be done in another routine, during vblank
User avatar
Drew Sebastino
Formerly Espozo
Posts: 3496
Joined: Mon Sep 15, 2014 4:35 pm
Location: Richmond, Virginia

Re: Palette Checker

Post by Drew Sebastino »

Well, I actually got around to trying to make it to where I have a giant table in ram that has information for all the object palettes that are going to be used in the game, (Each slot in the table consists of 2 bytes: one that says where the palette is in cgram, and one that says how many objects are using that palette. When the second number is 0, you remove the palette from cgram.) and I have the table that sees which of the 8 onscreen sprite palettes are occupied. Because the DMA routine needs to be during vblank, I made another table which has the addresses of any new palettes. (By the way, the address cannot be 0. Otherwise, the code thinks that there is no palette and quits.) I'm feeling rather lazy right now, so nothing is commented, but I can change that latter. (Oh yeah, one thing, how do you get both an 8 bit accumulator and index registers?)

Here's the beginning routine:

Code: Select all

.proc start_palette_checker
  rep #$30
  sep #$20   
  ldx #$00

prepare_for_palette_checker_loop:
  inx
  cpx #$08
  bcs palette_slot_not_found

palette_checker_loop:
  lda OnscreenPaletteTable,x
  bne prepare_for_palette_checker_loop

done:
  ldx
  asl
  stx TotalPalettesTable,y

  lda #$01
  sta OnscreenPaletteTable,x

  lda PaletteAddressTable,y
  sta DMAPaletteRequestTable,x
  inx
  sta DMAPaletteRequestCounter

palette_slot_not_found:
  rts

.endproc

PaletteAddressTable:
  .byte $??,$??,$??,$??,etc...
Here's the DMA routine:

Code: Select all

.proc start_palette_uploader
  rep #$30
  sep #$20 
  ldx #$00
  ldy #$00
  stz DMAPaletteRequestCounter
  
palette_uploader_loop:
  sty $2121

  lda DMAPaletteTable,x
  beq done:
  sta $4302
  lda #$01
  sta $4304
  lda #$10
  sta $4305

  stz $4300
  lda #$22
  sta $4301
  lda #$01
  sta $420B

  tya
  clc
  adc #$10
  tay
  inx
  cpx #$08
  bne palette_uploader_loop

done:
  rep #$30
  stz DMAPaletteTable
  stz DMAPaletteTable+2
  stz DMAPaletteTable+4
  stz DMAPaletteTable+6
  rts

.endproc
93143
Posts: 1715
Joined: Fri Jul 04, 2014 9:31 pm

Re: Palette Checker

Post by 93143 »

Espozo wrote:Oh yeah, one thing, how do you get both an 8 bit accumulator and index registers?
sep #$30

...or sep #%00110000, if you want it to be obvious what it's doing to the P register.
User avatar
Drew Sebastino
Formerly Espozo
Posts: 3496
Joined: Mon Sep 15, 2014 4:35 pm
Location: Richmond, Virginia

Re: Palette Checker

Post by Drew Sebastino »

Well, I'm 99.9% percent sure that this is good, because I wrote

Code: Select all

  ldy #$00
  jsr start_palette_checker
and it showed the correct palette in the palette 0 (for sprites). I then wrote

Code: Select all

  ldy #$00
  jsr start_palette_checker
  ldy #$02
  jsr start_palette_checker
and it also worked, having the second palette going to palette 1 because the first one was already occupied. Next, I copied this about 12 times or something and even though that is too many palettes for the SNES to handle for sprites, it didn't seem to mess anything up and instead just refused to try to load the palettes, like I want it to. I haven't linked the code with any of the objects yet, but I doubt it will do anything unexpected. Anyway, I have the code made, and I feel like sharing it, do feel free to use it. I can answer questions if anyone has any.

Here's the first part:

Code: Select all

.setcpu "65816"
.smart

; See Game.asm
.importzp DMAPaletteRequestCounter
.import TotalPalettesTable
.import OnscreenPaletteTable
.import DMAPaletteTable
.import Player1Palette, BulletPalette

; Used by Game.asm
.export start_palette_checker

.segment "CODE"

;
; start_palette_checker
; 
.proc start_palette_checker
  rep #$30
  sep #$10 
  ldx #$00

palette_checker_loop:
  lda OnscreenPaletteTable,x
  beq done

prepare_for_palette_checker_loop:
  inx
  cpx #$08
  bcs palette_slot_not_found
  bra palette_checker_loop     ;Bug fix by UnDisbeliever 

done:
  txa
  sta TotalPalettesTable,y
  
  lda #$01
  sta OnscreenPaletteTable,x

  rep #$30
  ldx DMAPaletteRequestCounter
  lda PaletteAddressTable,y
  sta DMAPaletteTable,x
  inx
  inx
  stx DMAPaletteRequestCounter

palette_slot_not_found:
  rts

.endproc

PaletteAddressTable:
  .word Player1Palette,BulletPalette (These can be whatever, of course.)
And here's the DMA part: (This goes under the vblank routine.)

Code: Select all

.setcpu "65816"
.smart

; See Game.asm
.importzp DMAPaletteRequestCounter
.import DMAPaletteTable

Player1Palette = 0
BulletPalette = 2

; Used by Game.asm
.export start_palette_uploader

.segment "CODE"

.proc start_palette_uploader
  sep #$30
  ldx #$00
  ldy #$80
  stz DMAPaletteRequestCounter
  
palette_uploader_loop:
  sty $2121

  rep #$30  
  lda DMAPaletteTable,x
  beq done
  sta $4302
  sep #$30
  lda #$00
  sta $4304
  lda #$20
  sta $4305

  stz $4300
  lda #$22
  sta $4301
  lda #$01
  sta $420B

  tya
  clc
  adc #$10
  tay
  inx
  inx
  cpx #$10
  bne palette_uploader_loop
  rep #$30

done:
  stz DMAPaletteTable
  stz DMAPaletteTable+2
  stz DMAPaletteTable+4
  stz DMAPaletteTable+6
  stz DMAPaletteTable+8
  stz DMAPaletteTable+10
  stz DMAPaletteTable+12
  stz DMAPaletteTable+14
  rts

.endproc
Last edited by Drew Sebastino on Mon Mar 30, 2015 5:03 am, edited 1 time in total.
UnDisbeliever
Posts: 123
Joined: Mon Mar 02, 2015 1:11 am
Location: Australia (PAL)
Contact:

Re: Palette Checker

Post by UnDisbeliever »

Espozo wrote:Well, I'm 99.9% percent sure that this is good, because I wrote

...


Here's the first part:
Umm I don't want to sound like a smarty-pants, but I think you kinda forgot to loop back to palette_checker_loop. Running your code in my head, the first time it executes it branches to done. The second time it falls into done with X = 1, because there is no loop.

I trust you when you said it works, did you submit the wrong code?

Also, you appear to be preforming a 16 bit check on OnscreenPaletteTable, even though it appears to use 8 bit values. This will cause problems when slot 2 is clear, but slot 3 is allocated.

Espozo wrote: Well, I actually got around to trying to make it to where I have a giant table in ram that has information for all the object palettes that are going to be used in the game
You appear to have TotalPalettesTable inside shadow RAM. I'm not sure how big it is, but I guess it would be 2*Number of Palettes and could easily grow to 256 bytes. Shadow RAM is precious, you only have ~7.5KiB of the stuff to play with.

May I recommend you move TotalPalettesTable to WRAM bank $7E or $7F and access it through long, X addressing. There would be some overhead, but as the start_palette_checker/palette_cleanup routines would only be called on object spawn and destroy it is well worth the RAM savings.
User avatar
Drew Sebastino
Formerly Espozo
Posts: 3496
Joined: Mon Sep 15, 2014 4:35 pm
Location: Richmond, Virginia

Re: Palette Checker

Post by Drew Sebastino »

UnDisbeliever wrote:but I think you kinda forgot to loop back to palette_checker_loop. Running your code in my head, the first time it executes it branches to done. The second time it falls into done with X = 1, because there is no loop. I trust you when you said it works, did you submit the wrong code?
No, that was the one I used, and I even assembled the game just now using it. (I'm really not even sure how it worked.)
UnDisbeliever wrote:Also, you appear to be preforming a 16 bit check on OnscreenPaletteTable, even though it appears to use 8 bit values. This will cause problems when slot 2 is clear, but slot 3 is allocated.
Where? I'm pretty sure the only place where I check onscreen palette table is here, and the accumulator is set to 8 bit.

Code: Select all

.proc start_palette_checker
  rep #$30
  sep #$10 
  ldx #$00

palette_checker_loop:
  lda OnscreenPaletteTable,x
  beq done
You appear to have TotalPalettesTable inside shadow RAM. I'm not sure how big it is, but I guess it would be 2*Number of Palettes and could easily grow to 256 bytes. Shadow RAM is precious, you only have ~7.5KiB of the stuff to play with.

May I recommend you move TotalPalettesTable to WRAM bank $7E or $7F and access it through long, X addressing. There would be some overhead, but as the start_palette_checker/palette_cleanup routines would only be called on object spawn and destroy it is well worth the RAM savings.
You're probably going to hate me for saying this, but what exactly is shadow ram? :oops:
UnDisbeliever
Posts: 123
Joined: Mon Mar 02, 2015 1:11 am
Location: Australia (PAL)
Contact:

Re: Palette Checker

Post by UnDisbeliever »

Espozo wrote: Where? I'm pretty sure the only place where I check onscreen palette table is here, and the accumulator is set to 8 bit.
No, SEP #$10 sets the Index Register to 8 bits. SEP #$20 sets the accumulator to 8 bit.
Espozo wrote: You're probably going to hate me for saying this, but what exactly is shadow ram? :oops:
Shadow RAM one of the names for the WRAM[1] mirror located in addresses $0000-$1FFF of Banks $00-$3F & $80-$BF. It contains a shadowed mirror of WRAM addresses $7E:0000 - $7E:1FFFF.

It contains the first 8KiB of WRAM and is the area Direct Page and the Stack are processed in. I usually allocate 512 128 bytes at $1F80-$1FFF for the stack leaving me with 7.8KiB to play with.

For most ca65 configs shadow RAM is the BSS segment.

[1] Work RAM.

EDIT: Changed stack size from 512 bytes to 128 based on Khaz's post
Last edited by UnDisbeliever on Mon Mar 30, 2015 9:09 am, edited 3 times in total.
User avatar
Khaz
Posts: 314
Joined: Thu Dec 25, 2014 10:26 pm
Location: Canada

Re: Palette Checker

Post by Khaz »

UnDisbeliever wrote:It contains the first 8KiB of WRAM and is the area Direct Page and the Stack are processed in. I usually allocate 512 bytes at $1E01-$1FFF for the stack leaving me with 7.5KiB to play with.
My god man what do you DO to your stack? I mean I have a mostly-functional game on my hands at this point and I don't think my stack ever gets lower than about $1FD0ish. I try to avoid using it at all except to preserve registers and occasionally quickly store a value I'm going to use again in a second...
Post Reply