How to show graphics on the screen?

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.
rchoudhary
Posts: 24
Joined: Wed Apr 10, 2019 4:24 pm

How to show graphics on the screen?

Post by rchoudhary »

I want to render this scene:

Image

I've watched the Super NES Features playlist on youtube by Retro Game Mechanics Explained, so I have a good high-level idea on how to accomplish this.

These are all the colors I use in the scene:

Image

The entire scene can be made with exactly 20 8x8 tiles and 24 16x16 tiles for a total of 44 tiles. I have all the tiles individually saved as colormapped (indexed) PNG files with transparency.

The background modes seemed kinda complicated, so I figured the easiest thing to do would be to use a constant color background and then just have everything else be a sprite. I have the precise x and y coordinates that all the sprites should be. I checked to make sure that no more than 32 objects are on any given scanline. There's also fewer than 128 objects on the screen in total.

I've also thought about how to animate each object:
  • 1. The flame for the candle is animated by simply flipping it back and forth horizontally (i.e. by using a simple EOR instruction).
    2. The shadows simply consist of a big shadow sprite covering a small one, and to toggle back and forth, I can just move the big sprite on and off the screen by toggling the MSb of the x coordinate (again with a simple EOR instruction).
    3. The balloons will be a bit more involved, but I think the easiest way would be for each balloon to maintain some dy value set to a constant, say 5. Every frame, I just toggle dy by doing dy = dy * -1. Then all I need to do to move the balloons appropriately is do ballonX += dy
    4. The confetti are simply animated by toggling the various tiles horizontally or vertically. I've got it worked out which ones are toggled which way.


However, I'm not sure how to proceed from here...

  • 1. How do I combine all these resources into a single ROM?
    2. How do I turn my colors into a color pallette and then get that into CGRAM?
    3. How do I initialize OAM? It holds data for all sprites, so do I have to "zero-initialize" the whole thing so it doesn't take any junk memory values and turn them into sprites?
    4. If I just want a plain background, do I still need to mess with background modes and make a background tile map and stuff?
    5. If I update the sprite info in OAM, do I need to do anything with VRAM and the DMA to get the sprites to update on the screen? Or does the PPU read OAM directly?
    6. What exactly is DMA used for during the VBlank period? The playlist I linked says it can be used to move large amounts of data to/from memory, but what exactly are we moving, and why? Would I need it?
    7. How do I get my tiles into VRAM to look like this:
    Image
Sorry for all these questions, but all the resources I've come across don't seem to drive these points home. And while they might cover the theory of the inner workings fairly well, they don't really talk about how to actually start showing stuff on the screen from a practical standpoint. Overall I just feel very lost... :(
creaothceann
Posts: 746
Joined: Mon Jan 23, 2006 7:47 am
Location: Germany
Contact:

Re: How to show graphics on the screen?

Post by creaothceann »

rchoudhary wrote:The entire scene can be made with exactly 20 8x8 tiles and 24 16x16 tiles for a total of 44 tiles. I have all the tiles individually saved as colormapped (indexed) PNG files with transparency.
Put all the graphics into one picture, replace the transparent pixels by the same color by e.g. putting a black layer as a background, combine all layers, and let your graphics program count the number of colors. If it's <= 16*15 + 1 (16 palette rows where each #0 is unused) then just reduce to that and use that as your palette (after converting the 8-bit values to 5-bit). If it's more than 128 colors you'll have to use one or more backgrounds.
rchoudhary wrote:I checked to make sure that no more than 32 objects are on any given scanline.
There's also the limit of 34 sprite tiles (8x8) per line.
rchoudhary wrote:
  • 1. How do I combine all these resources into a single ROM?
    2. How do I turn my colors into a color palette and then get that into CGRAM?
    3. How do I initialize OAM? It holds data for all sprites, so do I have to "zero-initialize" the whole thing so it doesn't take any junk memory values and turn them into sprites?
    4. If I just want a plain background, do I still need to mess with background modes and make a background tile map and stuff?
    5. If I update the sprite info in OAM, do I need to do anything with VRAM and the DMA to get the sprites to update on the screen? Or does the PPU read OAM directly?
    6. What exactly is DMA used for during the VBlank period? The playlist I linked says it can be used to move large amounts of data to/from memory, but what exactly are we moving, and why? Would I need it?
    7. How do I get my tiles into VRAM to look like this: https://i.imgur.com/w8jY4yO.png
  • 1. Look into the documentation of your assembler on how to include binary files.
    2. Personally I use an old copy of Paint Shop Pro 7, but that's not free (afaik). The palette files it creates are simple text files.
    3. Set the OAM index, write 512 bytes to the OAM data port (e.g. via DMA) during VBlank. See anomie's SNES documents on RHDN for more info.
    4. No. Afaik the backgrounds are disabled by default, but it can't hurt to initialize the relevant PPU registers at startup.
    5. You need to upload the graphics data to VRAM. Don't forget to set the PPU sprite registers so that they enable sprites on the mainscreen, and where to fetch the tiles from during rendering.
    6. Quickly transfer data from/to cartridge/WRAM/whatever to/from VRAM/OAM/CGRAM. You can for example transfer all your data from cartridge to WRAM, and then upload OAM/CGRAM data / changed VRAM data during VBlank.
    7. Create a 16*8 x Lines*8 picture, put your 8-bit tiles into it, and somehow convert it to a raw bitmap file (x*y / 2 bytes since it's 4bpp). Then include it into your source code.
You might eventually want to just create your own conversion tools...
My current setup:
Super Famicom ("2/1/3" SNS-CPU-GPM-02) → SCART → OSSC → StarTech USB3HDCAP → AmaRecTV 3.10
93143
Posts: 1830
Joined: Fri Jul 04, 2014 9:31 pm

Re: How to show graphics on the screen?

Post by 93143 »

creaothceann wrote:Create a 16*8 x Lines*8 picture, put your 8-bit tiles into it, and somehow convert it to a raw bitmap file (x*y / 2 bytes since it's 4bpp). Then include it into your source code.
Haven't you skipped a step?

The graphics need to be in SNES interleaved bitplane format. There are conversion programs available, like pcx2snes and gfx2snes; I prefer to use my own tools because the palette converter in pcx2snes is too clever by half. You may want to do the palette separately. To be fair I haven't tried gfx2snes...

Also, when initially loading VRAM, it's probably wise to force blank so your writes don't fail.
creaothceann
Posts: 746
Joined: Mon Jan 23, 2006 7:47 am
Location: Germany
Contact:

Re: How to show graphics on the screen?

Post by creaothceann »

93143 wrote:
creaothceann wrote:Create a 16*8 x Lines*8 picture, put your 8-bit tiles into it, and somehow convert it to a raw bitmap file (x*y / 2 bytes since it's 4bpp). Then include it into your source code.
Haven't you skipped a step?
:oops:
My current setup:
Super Famicom ("2/1/3" SNS-CPU-GPM-02) → SCART → OSSC → StarTech USB3HDCAP → AmaRecTV 3.10
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: How to show graphics on the screen?

Post by koitsu »

Try https://github.com/Optiroc/SuperFamiconv

Given the low amount of animation (2 frames), sprites are probably overkill for this. You'll have more than enough PPU RAM for this project.

Mode 1 would probably be fine, and use of only 1 background (or use 2, putting the confetti on BG1 and the balloons on BG2, since it looks to me like the confetti is "in front of" the balloons), and 32x32 size (single-screen).

Pre-organise your tile map (a.k.a. nametable or "screen data" in official Nintendo terminology) for both BGs somewhere in ROM -- it'll be half of a screen (from the looks of it, the top half) -- and then just do DMA transfers of the tile map data to PPU RAM every N frames (it seems to be "animating" once a second, so every 60 frames on NTSC or 50 on PAL if you care about that); you can DMA up to about 6KBytes of data per frame/NMI/VBlank, and a single screen of tile map data in mode 1 is $800 (2048) bytes of data, so you have more than enough time in a single VBlank.

If you *didn't* have enough time, considering the low animation rate, you could split it up across frames by using two separate 32x32 screens in a "double buffering" type of approach, then just tweak $2107/8 during VBlank to "switch" which screen is shown, doing multiple DMA transfers to the non-visible screen throughout the remaining 59 (NTSC) or 49 (PAL) frames. Like I said: lots and lots of time.
rchoudhary
Posts: 24
Joined: Wed Apr 10, 2019 4:24 pm

Re: How to show graphics on the screen?

Post by rchoudhary »

koitsu wrote: Mode 1 would probably be fine, and use of only 1 background (or use 2, putting the confetti on BG1 and the balloons on BG2, since it looks to me like the confetti is "in front of" the balloons), and 32x32 size (single-screen).

Pre-organise your tile map (a.k.a. nametable or "screen data" in official Nintendo terminology) for both BGs somewhere in ROM -- it'll be half of a screen (from the looks of it, the top half) -- and then just do DMA transfers of the tile map data to PPU RAM every N frames
That sounds more complicated to me than setting up and updating some sprites :?

But in the meantime, I've got a 128x256 pixel (i.e. 16x32 tiles) tilemap set up:

Image

The red borders aren't in the actual tilemap, this is an annotated version for display purposes.

What programs do I use to convert this into 4BPP interleaved sprite data? And how do I take color palettes into account? For example, the three balloons use the same tiles (the brown ones in the tilesets) but they would use 3 different palettes.

I looked at superfamiconv and I'm really confused as to what exactly the inputs are and what the outputs will be... Are there any tutorials or examples?
creaothceann wrote: 6. Quickly transfer data from/to cartridge/WRAM/whatever to/from VRAM/OAM/CGRAM. You can for example transfer all your data from cartridge to WRAM, and then upload OAM/CGRAM data / changed VRAM data during VBlank.
So if I'm understanding this right, the process is something like this:
  • 1. During startup, DMA is used to transfer all my graphics from the cartridge to VRAM, all my palettes to CGRAM, and initialize OAM with the cartridge contents as well
    2. I can initialize WRAM with any values I am working with, such as the initial positions of / any status variables regarding my sprites.
    3. During every frame I can update the sprites in memory, and then during each VBlank period I use DMA to update the info in OAM
Why would I need to update VRAM or CGRAM during "gameplay"? Aren't the assets of a game fixed? Or can they change? And if they can change, would I need to worry about that?
niconii
Posts: 219
Joined: Sun Mar 27, 2016 7:56 pm

Re: How to show graphics on the screen?

Post by niconii »

rchoudhary wrote:So if I'm understanding this right, the process is something like this:
  • 1. During startup, DMA is used to transfer all my graphics from the cartridge to VRAM, all my palettes to CGRAM, and initialize OAM with the cartridge contents as well
    2. I can initialize WRAM with any values I am working with, such as the initial positions of / any status variables regarding my sprites.
    3. During every frame I can update the sprites in memory, and then during each VBlank period I use DMA to update the info in OAM
From the way you described it, I'm not sure if you're under the impression that step 1 happens automatically, but this is something you have to do yourself, writing your own code to initialize all registers and memory (the SNES boots up with a lot of things in a random state) and transferring everything you need to. Otherwise this seems correct.
rchoudhary wrote:Why would I need to update VRAM or CGRAM during "gameplay"?
Several reasons. VRAM can only store 64 KB of data, but games can be up to 4 MB (or even higher in rare cases). It's very common that you'll need to switch out what's in VRAM to load in, say, graphics for a different level or area, new enemies as they spawn in, even different player sprites if they can't all fit into VRAM at once (e.g. Kirby Super Star). Furthermore, not only the tiles themselves but also the layout of tiles on the screen is stored in VRAM, so naturally you'll be updating that constantly as the screen scrolls around, switching to different maps, etc. Similarly, CGRAM stores the current 256-color palette, and that's likely to change often throughout the game, too.
rchoudhary wrote:Aren't the assets of a game fixed? Or can they change? And if they can change, would I need to worry about that?
Assets are not fixed. Since graphics are stored in RAM, you can change them whenever and however you want. Think about Mario Paint, for instance. The player can draw whatever they want, and the game updates the tiles accordingly. I don't know what you mean by "worrying about that", though. It all depends on what you want to do.
93143
Posts: 1830
Joined: Fri Jul 04, 2014 9:31 pm

Re: How to show graphics on the screen?

Post by 93143 »

rchoudhary wrote:Why would I need to update VRAM or CGRAM during "gameplay"? Aren't the assets of a game fixed? Or can they change? And if they can change, would I need to worry about that?
In your case, there's not much "gameplay" to speak of, and the desired result is so simple that there are several relatively straightforward ways to generate it, so it depends on how exactly you propose to achieve your goal. Using sprites, you shouldn't ever have to change VRAM or CGRAM. Using BG layers, you shouldn't ever have to change OAM or CGRAM.

Let me just note here that you are using the word "tilemap" wrong. That word is already spoken for; it means the data table that tells the video chip how to render a BG layer (it's a list of values specifying tile number, subpalette number, flip bits, and priority). What you're showing is the actual pool of tiled graphics data that the numbers in the tilemap refer to. Nintendo has their own terms for this stuff, but if you attempt to use a commonly-used word or phrase to mean something other than what everyone else uses it for, it's going to cause needless misunderstandings.

Anyway, basically the tilemap is the equivalent of OAM, but for BG layers, and it's in VRAM. That's why you'd have to write to VRAM if you were to go with koitsu's method.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: How to show graphics on the screen?

Post by koitsu »

rchoudhary wrote:That sounds more complicated to me than setting up and updating some sprites :?
Not to me. A single DMA transfer (to copy from ROM to PPU RAM) every 50 or 60 frames is a lot less effort, IMO, than managing batches of sprites. I think you'll find OAM/OBJ quite daunting once you get into it, plus sprites get their own palettes, so now you have to manage two separate palette sets in CGRAM.

Step back for a moment and think about what you're depicting in the animated GIF: literally 2 frames of animation, once a second. Ask yourself: why do you need sprites to accomplish this task? Unless, of course, you have other desires than whats depicted?
rchoudhary wrote:What programs do I use to convert this into 4BPP interleaved sprite data? And how do I take color palettes into account? For example, the three balloons use the same tiles (the brown ones in the tilesets) but they would use 3 different palettes.

I looked at superfamiconv and I'm really confused as to what exactly the inputs are and what the outputs will be... Are there any tutorials or examples?
SuperFamiconv is what you want, I assure you. Most all other graphical tools for working with SNES graphics are junk; don't let anyone tell you YY-CHR will do the trick for you, because it won't (it does not manage tile map/screen layout data, only actual CHR). There is an "Examples" entry in the SuperFamiconv README.md which I suspect you overlooked.

I've thought about this a bit. The cheapest/easiest way to do this, particularly since there's so little animation, is to not even bother with putting the confetti on its own BG -- just sort of "brute force" the entire thing: put all the graphics/data into PPU RAM, stick one frame of animation on BG1, and the other on BG2, then "flip" between the two by turning one off and the other on, etc..

I recommend your process be this:

1. Save frame 0 to its own file: frame0.png
2. Save frame 1 to its own file: frame1.png

These images need to share the same indexed palette. I can't tell how many colours there are, so I'll assume 64. Remember colour #0 is to be treated as the transparent colour. Make sure your PNGs are 256x224.

3. Use SuperFamiconv to convert the graphics appropriately. You'll only need the palette file from one, since all the graphics share the same palette:

Code: Select all

superfamiconv -B 4 -i frame0.png -p palette.bin -t frame0.chr -t frame0.map
superfamiconv -B 4 -i frame1.png -t frame1.chr -t frame1.map
You now have 7 files to use for your graphics: 3 files per frame (thus 6 files), and 1 file for the palette.

All below code assumes A = 8-bit, X/Y = 16-bit, and use of the ca65 assembler. If you're using a different assembler then this code will not work.

4. Initialise SNES registers per official documentation. You can find routines to do this, and hopefully properly. This is a one-time thing you do ONCE at reset, so don't let anyone push you around about "the most optimised reset routine" -- it's nonsense. A standard/simple reset routine is about 92-93 lines of code. This is literally copy-paste type of stuff, seriously.

Also: make sure the screen is OFF as well (write $8F to $2100). Bit 7 being set is the important part. If you fail to do this, all your DMA transfers will probably result in gobbledegook. You'll turn the screen on later.

5. Add the raw data files into your ROM using .incbin or equivalent like so. You may need to use different segments (banks) since these files may be too large to fit into a single 32KByte bank (mode 20 uses 32KB banks, more or less). Also add a byte of zero data to the ROM so that you can use it to zero PPU RAM via DMA:

Code: Select all

palette: .incbin "palette.bin"
palettelen = * - palette

frame0map: .incbin "frame0.map"
frame0maplen = * - frame0map

frame0chr: .incbin "frame0.chr"
frame0chrlen = * - frame0chr

frame1map: .incbin "frame1.map"
frame1maplen = * - frame1map

frame1chr: .incbin "frame1.chr"
frame1chrlen = * - frame1chr

zerobyte: .byte $00
6. Zero PPU RAM (all 64KBytes of it) using a DMA transfer, something like ldx #$0000 / stx $2116 / lda #%00001001 / sta $4300 / lda #$18 / sta $4301 / ldx #.loword(zerobyte) / stx $4302 / lda #.bankbyte(zerobyte) ; Source bank / sta $4304 / ldx #$0000 / stx $4305 / lda #$01 / sta $420B

7. Set up PPU RAM for tile map and CHR data. Tile map data requires "1K-word" (2048 bytes) alignment, and CHR data requires "4K-word" (8192 bytes) alignment. Remember: SNES PPU RAM addresses tend to work in WORDS, not in BYTES, and this can cause a lot of confusion/pain. GET FAMILIAR WITH YOUR EMULATOR'S DEBUGGER. If it doesn't have one, GET ONE (hint: bsnes-plus or Mesen-S). A PPU RAM layout might be something like this (and these offsets are listed IN BYTES):

Code: Select all

; PPU RAM map
; -----------
; $0000-07FF = BG1 map data (32x32, single screen) -- frame 0
; $1000-17FF = BG2 map data (32x32, single screen) -- frame 1
; $2000-???? = BG1 CHR data -- frame 0
; $8000-???? = BG2 CHR data -- frame 1
The code for this setup:

Code: Select all

lda #$00    ; BG1 tile map = PPU RAM $0000-07FF, single 32x32 screen
sta $2107
lda #$08    ; BG2 tile map = PPU RAM $1000-17FF, single 32x32 screen
sta $2108

lda #$41   ; BG1 CHR = PPU RAM $2000 ($2000/2 = $1), BG2 CHR = PPU RAM $8000 ($8000/2=$4)
sta $210b
8. DMA transfer the tile map data and CHR data from ROM into PPU RAM:

Code: Select all

lda #$0000   ; BG1 tile map (PPU RAM $0000)
stx $2116
ldx #$1801
stx $4300
ldx #.loword(frame0map)
stx $4302
ldx #.bankbyte(frame0map)
sta $4304
ldx #frame0maplen
stx $4305
lda #$01
sta $420B

lda #$0800   ; BG2 tile map (PPU RAM $1000)
stx $2116
ldx #$1801
stx $4300
ldx #.loword(frame1map)
stx $4302
ldx #.bankbyte(frame1map)
sta $4304
ldx #frame1maplen
stx $4305
lda #$01
sta $420B

lda #$1000   ; BG1 CHR (PPU RAM $2000)
stx $2116
ldx #$1801
stx $4300
ldx #.loword(frame0chr)
stx $4302
ldx #.bankbyte(frame0chr)
sta $4304
ldx #frame0chrlen
stx $4305
lda #$01
sta $420B

lda #$4000   ; BG2 CHR (PPU RAM $8000)
stx $2116
ldx #$1801
stx $4300
ldx #.loword(frame1chr)
stx $4302
ldx #.bankbyte(frame1chr)
sta $4304
ldx #frame1chrlen
stx $4305
lda #$01
sta $420B
9. DMA transfer the palette data into CGRAM:

Code: Select all

lda #$00   ; start at palette entry #0
sta $2121
ldx #$2200
stx $4300
ldx #.loword(palette)
stx $4302
lda #.bankbyte(palette)
sta $4304
ldx #palettelen
stx $4305
lda #$01
sta $420B
10. You'll need one variable in direct page or RAM, and a couple equates. The equates control what goes into $212c, which is what controls which BGs are visible. The goal is to toggle between BG1 and BG2, with 60 frames (1 second) delay in between.

Code: Select all

showbg: .res 1     ; This needs to go in your ZEROPAGE or RAM segment or whatever you call it; 1-byte variable

showbg1 = %00000001
showbg2 = %00000010
11. Enable display of BG1 (frame 0), enable VBlank-on-NMI and automatic joypad reading (if you ever want it), and turn on the screen. Also set up a couple equates which we'll use later on:

Code: Select all

lda #showbg1
sta showbg
sta $212c

lda #$81
sta $4200

lda #$0f
sta $2100
12. At this point, frame 0 should be visible. Time to do the animation. This is pretty easy: you just flip-flop between two values written to $212c. You need to make sure that your NMI vector points to the code at the NMI label below.

Code: Select all

mainloop:
  ldx #0
waitnmi:
  wai
  inx
  cpx #60       ; wait 60 frames
  bne waitnmi

; Flip-flop $212c between values showbg1 and showbg2
  lda showbg
  cmp #showbg1
  beq show_frame_2
  lda #showbg1
  sta $212c
  bra next
show_frame_2:
  lda #showbg2
  sta $212c

next:
  bra mainloop

NMI:
  pha
  lda showbg
  sta $212c
  pla
  rti
There are several things omitted from this example which are for you to figure out/do on your own, such as setting up the RESET vector, ensuring register sizes are proper (via sep/rep) and so on. But the overall model should work fine.

Edit: fixing up some stuff. Probably mistakes in here too. Oh well :D
rchoudhary
Posts: 24
Joined: Wed Apr 10, 2019 4:24 pm

Re: How to show graphics on the screen?

Post by rchoudhary »

So I've been following along with what you said. I made the two frames as PNGs and converted them with superfamiconv:

Code: Select all

(base) graphics $ superfamiconv -p palette.pal -i frame0.png -t frame0.chr -m frame0.map -v
Loaded image from "frame0.png" (256x224, indexed color)
Mapping optimized palette (8x16 entries for 8x8 tiles)
Setting color zero to #f5f5f5ff
Generated palette with [16,6] colors, 22 total
Created optimized tileset with 190 tiles (discared 706 redundant tiles)
Mapping 896 (8x8) image slices
Saved native palette data to "palette.pal"
Saved native tile data to "frame0.chr"
Saved native map data to "frame0.map"
(base) graphics $ superfamiconv -i frame1.png -t frame1.chr -m frame1.map -v
Loaded image from "frame1.png" (256x224, indexed color)
Mapping optimized palette (8x16 entries for 8x8 tiles)
Setting color zero to #f5f5f5ff
Generated palette with [16,9] colors, 25 total
Created optimized tileset with 181 tiles (discared 715 redundant tiles)
Mapping 896 (8x8) image slices
Saved native tile data to "frame1.chr"
Saved native map data to "frame1.map"
(base) graphics $ 
Then based on the code you gave I have this for main.asm

Code: Select all

.define ROM_NAME "GREEN"
.include "lorom128.inc"

palette: .incbin "graphics/palette.pal"
palettelen = * - palette

frame0map: .incbin "graphics/frame0.map"
frame0maplen = * - frame0map

frame0chr: .incbin "graphics/frame0.chr"
frame0chrlen = * - frame0chr

frame1map: .incbin "graphics/frame1.map"
frame1maplen = * - frame1map

frame1chr: .incbin "graphics/frame1.chr"
frame1chrlen = * - frame1chr

zerobyte: .byte $00

bgstatus: .res 1

showbg1 = %00000001
showbg2 = %00000010

reset:

    init_cpu

    sep         #%00100000              ; A reg in 8-bit mode

    ; Force blank

    lda         #$80
    sta         $2100

    ; Zero-initialize VRAM via DMA

    ldx         #$0000
    stx         $2116                   ; Starting address in VRAM
    lda         #%00001001
    sta         $4300                   ; DMA properties: CPU to PPU, no increment, 2 bytes to 2 registers
    lda         #$18
    sta         $4301                   ; Set write address to $2118
    ldx         #.loword(zerobyte)
    stx         $4302                   ; Set source address to the zero byte
    lda         #.bankbyte(zerobyte)
    sta         $4304                   ; Set source address to the zero byte
    ldx         #$0000
    stx         $4305                   ; Writing 0 bytes??
    lda         #%00000001
    sta         $420B                   ; Enable DMA channel 0

    ; Set up tile map for backgrounds

    lda         #$00                    ; Set BG1 tile map at VRAM $0000-07FF, 32x32
    sta         $2107
    lda         #$08                    ; Set BG2 tile map at VRAM $1000-17FF, 32x32
    sta         $2108
    lda         #$41                    ; Set BG1 CHR at $2000 and BG2 CHR at $8000
    sta         $210b

    ; Copy BG1 tile map to VRAM at $0000 via DMA

    lda         #$0000
    stx         $2116
    ldx         #$1801
    stx         $4300
    ldx         #.loword(frame0map)
    stx         $4302
    ldx         #.bankbyte(frame0map)
    sta         $4304
    ldx         #frame0maplen
    stx         $4305
    lda         #$01
    sta         $420B

    ; Copy BG2 tile map to VRAM at $1000 via DMA

    ldx         #$0800
    stx         $2116
    ldx         #$1801
    stx         $4300
    ldx         #.loword(frame1map)
    stx         $4302
    ldx         #.bankbyte(frame1map)
    sta         $4304
    ldx         #frame1maplen
    stx         $4305
    lda         #$01
    sta         $420B

    ; Copy BG1 CHR to VRAM at $2000 via DMA

    ldx         #$1000
    stx         $2116
    ldx         #$1801
    stx         $4300
    ldx         #.loword(frame0chr)
    stx         $4302
    ldx         #.bankbyte(frame0chr)
    sta         $4304
    ldx         #frame0chrlen
    stx         $4305
    lda         #$01
    sta         $420B

    ; Copy BG2 CHR to VRAM at $8000 via DMA

    ldx         #$4000
    stx         $2116
    ldx         #$1801
    stx         $4300
    ldx         #.loword(frame1chr)
    stx         $4302
    ldx         #.bankbyte(frame1chr)
    sta         $4304
    ldx         #frame1chrlen
    stx         $4305
    lda         #$01
    sta         $420B

    ; Copy palette to CGRAM via DMA

    lda         #$00   ; start at palette entry #0
    sta         $2121
    ldx         #$2200
    stx         $4300
    ldx         #.loword(palette)
    stx         $4302
    lda         #.bankbyte(palette)
    sta         $4304
    ldx         #palettelen
    stx         $4305
    lda         #$01
    sta         $420B

    ; Enable BG1

    lda         #showbg1
    sta         bgstatus
    sta         $212c

    ; Enable VBlank interrupt

    ; lda         #$80
    ; sta         $4200


    ; Set background color to "white smoke"

    sep         #$20                    ; Set the A register to 8-bit.
    lda         #$DE
    sta         $2122
    lda         #$7B
    sta         $2122

    ; End force blank

    lda         #$0F
    sta         $2100

@loop:
    jmp @loop
For some reason though, no graphics are being shown. However I am seeing the solid background color (although not much of a feat...). I've had to comment out the lines that enable the VBlank NMI because it would just cause the screen to go black. I imagine it's because I don't have a VBlank interrupt handler. I tried adding

Code: Select all

VBlank:
    rti
in my code but it didn't help. Is there some other setup I have to do?

But anyways, what is the reason why I don't see BG1 (if it's blatantly obvious)? Did I mess up with the banks or something? I'm a little lost. I've double checked my code but I feel like the error lies outside of what I can even think of to check. I'm using the BSNES emulator.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: How to show graphics on the screen?

Post by koitsu »

1. You're using some templating thing you got off of https://wiki.superfamicom.org/basic-ca6 ... rogramming . I've never used this nor have any familiarity with it. One thing I see immediately wrong with their code there is that "Clear PPU registers" routine/loop -- no no NO! (Remember what I said earlier about people messing around with the SNES init routines for no reason? This is one of many examples.)

But from the example code there, it's pretty obvious they don't bother to explain the 6502/65816 vectors at all, instead shoving them into a nebulous segment and let you figure it out yourself. I'm one of those people who doesn't tend to use "pre-made stuff" off the Internet because I find the stuff generally sub-par quality.

You could replace .segment "VECTORS" with the following:

Code: Select all

.segment "VECTORS"
    .word emptyvect               ; $FFE4: native COP
    .word emptyvect               ; $FFE6: native BRK
    .word emptyvect               ; $FFE8: native ABORT
    .word NMI                     ; $FFEA: native NMI
    .word $0000                   ; $FFEC: n/a
    .word emptyvect               ; $FFEE: native IRQ
    .word $0000                   ; $FFF0: n/a
    .word $0000                   ; $FFF2: n/a
    .word emptyvect               ; $FFF4: emulation COP
    .word $0000                   ; $FFF6: n/a
    .word emptyvect               ; $FFF8: emulation ABORT
    .word NMI                     ; $FFFA: emulation NMI
    .word RESET                   ; $FFFC: emulation RESET
    .word emptyvect               ; $FFFE: emulation IRQ/BRK
Then make sure:

i. Your label called reset is renamed to RESET
ii. Make your own label called NMI and put the code I stated there -- this is your code that runs once per VBlank. This label should be somewhere *after* your @loop: jmp @loop code
iii. Make a label called emptyvect that just does rti and nothing else.
iv. Edit lorom128.cfg and make sure VECTORS in SEGMENTS uses start = $FFE4 not start = $FFE0. Failure to do this will quite literally break everything

2. I don't know what the init_snes macro contains, code-wise. It may be setting the A/X/Y registers to a size that causes your DMA transfers to incorrectly behave (e.g. X/Y being 8-bit would do that). The example web page only has init_cpu, which is a macro that does clc / xce / rep #$10 / sep #$20 (which are good instructions to start off with for sure). A common start-up routine on reset looks like this, without all the macro crap:

Code: Select all

RESET:
    sei
    phk
    plb
    clc
    xce
    rep #$30      ; 16-bit A/X/Y
    lda #$0000    ; Direct page @ $0000
    tcd
    ldx #$01ff    ; Stack @ $0100-01ff
    txs
    sep #$20      ; 8-bit A

    ...
    ... SNES initialisation code here (i.e. what I give below)
    ...

    ...
    ... Register tweaking and DMA code here (i.e. all the DMA transfers and $212c, $4200, etc. tweaks)
    ...

    lda #$01      ; Use graphics mode 1
    sta $2105

    cli

    lda #$81      ; Enable NMI-on-VBlank, automatic joypad read
    sta $4200

    lda #$0f      ; Enable screen
    sta $2100

@loop:
    wai           ; Wait for NMI

    ...
    ... Do other things here as needed
    ...

    jmp @loop
Note that in the above code I added lda #$01 / sta $2105. Lack of this could be the source of the problem; I suspect the code you're using is still using mode 0 for graphics, which are in a completely different format (2bpp rather than 4bpp).

Regardless, mode 1 is certainly what you want here, as your graphics were converted into a mode 1 / 4bpp format. That's the default for SuperFamiconv, unless you use the -B xxx flag to select a different bit depth. For example, BG3 in mode 1 uses 2bpp rather than 4bpp, so if you ever wanted to use the 3rd BG you'd need to use -B 2 for said BG's graphics.

Here's a proper SNES init routine you can use. This assumes 8-bit accumulator.

Code: Select all

    lda #$8f        ; forced blanking (screen off), full brightness
    sta $2100       ; brightness & screen enable register
    lda #$00
    sta $2101       ; sprite register (size & address in VRAM)
    sta $2102       ; sprite registers (address of sprite memory [OAM])
    sta $2103       ; sprite registers (address of sprite memory [OAM])
    sta $2105       ; graphic mode register
    sta $2106       ; mosaic register
    sta $2107       ; plane 0 map VRAM location
    sta $2108       ; plane 1 map VRAM location
    sta $2109       ; plane 2 map VRAM location
    sta $210A       ; plane 3 map VRAM location
    sta $210B       ; plane 0 & 1 Tile data location
    sta $210C       ; plane 2 & 3 Tile data location
    sta $210D       ; plane 0 scroll x (first 8 bits)
    sta $210D       ; plane 0 scroll x (last 3 bits)
    sta $210E       ; plane 0 scroll y (first 8 bits)
    sta $210E       ; plane 0 scroll y (last 3 bits)
    sta $210F       ; plane 1 scroll x (first 8 bits)
    sta $210F       ; plane 1 scroll x (last 3 bits)
    sta $2110       ; plane 1 scroll y (first 8 bits)
    sta $2110       ; plane 1 scroll y (last 3 bits)
    sta $2111       ; plane 2 scroll x (first 8 bits)
    sta $2111       ; plane 2 scroll x (last 3 bits)
    sta $2112       ; plane 2 scroll y (first 8 bits)
    sta $2112       ; plane 2 scroll y (last 3 bits)
    sta $2113       ; plane 3 scroll x (first 8 bits)
    sta $2113       ; plane 3 scroll x (last 3 bits)
    sta $2114       ; plane 3 scroll y (first 8 bits)
    sta $2114       ; plane 3 scroll y (last 3 bits)
    lda #$80        ; increase VRAM address after writing to $2119
    sta $2115       ; VRAM address increment register
    lda #$00
    sta $2116       ; VRAM address low
    sta $2117       ; VRAM address high
    sta $211A       ; initial mode 7 setting register
    sta $211B       ; mode 7 matrix parameter A register (low)
    lda #$01
    sta $211B       ; mode 7 matrix parameter A register (high)
    lda #$00
    sta $211C       ; mode 7 matrix parameter B register (low)
    sta $211C       ; mode 7 matrix parameter B register (high)
    sta $211D       ; mode 7 matrix parameter C register (low)
    sta $211D       ; mode 7 matrix parameter C register (high)
    sta $211E       ; mode 7 matrix parameter D register (low)
    lda #$01
    sta $211E       ; mode 7 matrix parameter D register (high)
    lda #$00
    sta $211F       ; mode 7 center position X register (low)
    sta $211F       ; mode 7 center position X register (high)
    sta $2120       ; mode 7 center position Y register (low)
    sta $2120       ; mode 7 center position Y register (high)
    sta $2121       ; color number register ($00-$ff)
    sta $2123       ; bg1 & bg2 window mask setting register
    sta $2124       ; bg3 & bg4 window mask setting register
    sta $2125       ; obj & color window mask setting register
    sta $2126       ; window 1 left position register
    sta $2127       ; window 2 left position register
    sta $2128       ; window 3 left position register
    sta $2129       ; window 4 left position register
    sta $212A       ; bg1, bg2, bg3, bg4 window logic register
    sta $212B       ; obj, color window logic register (or, and, xor, xnor)
    sta $212C       ; main screen designation (planes, sprites enable)
    sta $212D       ; sub screen designation
    sta $212E       ; window mask for main screen
    sta $212F       ; window mask for sub screen
    lda #$30
    sta $2130       ; color addition & screen addition init setting
    lda #$00
    sta $2131       ; add/sub sub designation for screen, sprite, color
    lda #$E0
    sta $2132       ; color data for addition/subtraction
    lda #$00
    sta $2133       ; screen setting (interlace x,y/enable SFX data)
    sta $4200       ; enable v-blank, interrupt, joypad register
    lda #$FF
    sta $4201       ; programmable I/O port
    lda #$00
    sta $4202       ; multiplicand A
    sta $4203       ; multiplier B
    sta $4204       ; multiplier C
    sta $4205       ; multiplicand C
    sta $4206       ; divisor B
    sta $4207       ; horizontal count timer
    sta $4208       ; horizontal count timer MSB
    sta $4209       ; vertical count timer
    sta $420A       ; vertical count timer MSB
    sta $420B       ; general DMA enable (bits 0-7)
    sta $420C       ; horizontal DMA (HDMA) enable (bits 0-7)
    sta $420D       ; access cycle designation (slow/fast rom)
3. You can't just throw bgstatus: .res 1 in somewhere randomly. I called the variable showbg in my example, and told you in the comments clearly that it needed to go into your ZEROPAGE or RAM segment. From the look of lorom128.cfg, it needs to go into the ZEROPAGE segment. So at the top of your code -- AND IT MUST BE AT THE TOP -- you need to do:

Code: Select all

.segment "ZEROPAGE"
bgstatus: .res 1

.segment "CODE"

RESET:
    ...
4. Your graphics are being inserted directly into the ROM via .incbin with no respect for mode 20/LoROM banking. I'm actually surprised the code assembles at all (it's going to depend on what the resulting filesizes are. If they're all small enough to fit within 32KBytes, then it's possible).

I would suggest putting the graphics in their own segment(s). lorom128.cfg appears to create a couple extra banks/segments called BANK1, BANK2, and BANK3. You could do something like this -- again, these need to go at the top of the file (i.e. before .segment "ZEROPAGE"):

Code: Select all

.segment "BANK1"
palette: .incbin "graphics/palette.pal"
palettelen = * - palette

frame0map: .incbin "graphics/frame0.map"
frame0maplen = * - frame0map

frame0chr: .incbin "graphics/frame0.chr"
frame0chrlen = * - frame0chr

.segment "BANK2"
frame1map: .incbin "graphics/frame1.map"
frame1maplen = * - frame1map

frame1chr: .incbin "graphics/frame1.chr"
frame1chrlen = * - frame1chr
I also suggest putting zerobyte in one of those banks as well. Currently it's just sitting in, presumably, your CODE segment, which is fine but usually things like ROM data should go in RODATA or some other place.

5. I don't know why you're playing with $2122, re: "white smoke". Your palette is already set up in the DMA transfer. All you're doing in that code is inserting one more colour in CGRAM at wherever the palette DMA transfer left off ($2121-wise).

6. Did you try loading the ROM up in an emulator with a debugger, ex. bsnes-plus (as bsnes does not have a debugger), and step through the instructions to see what's going on? If not, why not? Doing so will let you see what ends up in PPU RAM, CGRAM, etc..

There is certainly a bug somewhere, but I can't tell you where because I don't have the graphics in question. It could be something as simple as one of the PPU RAM addresses being wrong/a mistake on my part. My usual debugging process involves recreating the users' development environment and rebuilding the ROM, then stepping through it in a debugger to find out what's incorrect, then meticulously documenting what the mistakes were. If you were to upload a .zip file of the graphics (including the source PNGs), I could take a look, time permitting.

But that lorom128.cfg ld65 configuration file lets you get away with some "incorrect" stuff. I use my own that's a bit more strict, i.e. will catch errors/things during link-time a lot easier. I can't just paste the one I use because it would require me to literally change all the segment names/bits/etc. and explain it all to you in the process.

A lot of the annoyances/complexities here (ex. segments, etc.) stem from ca65/ld65. You'll have to get used to them. The alternate assembler, WLA DX, is equally complicated just in a very different way. I don't particularly like either of them very much (there currently isn't a good alternate to either, but there's a new assembler in the works that may come out soon), but if I have to choose, I'll pick ca65/ld65 for sure.
Last edited by koitsu on Tue Apr 30, 2019 5:51 am, edited 1 time in total.
User avatar
TOUKO
Posts: 306
Joined: Mon Mar 30, 2015 10:14 am
Location: FRANCE

Re: How to show graphics on the screen?

Post by TOUKO »

The easiest way to render this is to use 2 frames, as there is only 2 frames of animation you can switch between the both by changing only the vertical scrolling(you make 2 pictures of 256x224 pixels,one for each animation) .
Alternate the scrolling between 0 and 224 with a frame counter will doing the job,and the difficult part should be the palettes,but not a big deal .
rchoudhary
Posts: 24
Joined: Wed Apr 10, 2019 4:24 pm

Re: How to show graphics on the screen?

Post by rchoudhary »

Ok so I have the following in lorom128.inc:

Code: Select all

.segment "VECTORS"
    .word Empty_Handler           ; $FFE4: native COP
    .word Empty_Handler           ; $FFE6: native BRK
    .word Empty_Handler           ; $FFE8: native ABORT
    .word NMI                     ; $FFEA: native NMI
    .word $0000                   ; $FFEC: n/a
    .word Empty_Handler           ; $FFEE: native IRQ
    .word $0000                   ; $FFF0: n/a
    .word $0000                   ; $FFF2: n/a
    .word Empty_Handler           ; $FFF4: emulation COP
    .word $0000                   ; $FFF6: n/a
    .word Empty_Handler           ; $FFF8: emulation ABORT
    .word NMI                     ; $FFFA: emulation NMI
    .word Reset                   ; $FFFC: emulation RESET
    .word Empty_Handler           ; $FFFE: emulation IRQ/BRK
and I set VECTORS: load = ROM, start = $FFE4; in lorom128.cfg. Here is my full main.asm file:

Code: Select all

.define ROM_NAME "TEST"
.include "lorom128.inc"

.segment "BANK1"

palette: .incbin "img/palette.pal"
palettelen = * - palette

frame0map: .incbin "img/frame0.map"
frame0maplen = * - frame0map

frame0chr: .incbin "img/frame0.chr"
frame0chrlen = * - frame0chr

.segment "BANK2"

frame1map: .incbin "img/frame1.map"
frame1maplen = * - frame1map

frame1chr: .incbin "img/frame1.chr"
frame1chrlen = * - frame1chr

zerobyte: .byte $00

.segment "ZEROPAGE"

bgstatus: .res 1

showbg1 = %00000001
showbg2 = %00000010

.segment "CODE"

Reset:

    ; SNES initialization

    sei
    phk
    plb
    clc
    xce
    rep         #$30                    ; 16-bit A/X/Y
    lda         #$0000                  ; Direct page @ $0000
    tcd
    ldx         #$01ff                  ; Stack @ $0100-01ff
    txs
    sep         #%00100000              ; 8-bit A

    lda         #$8F                    ; forced blanking (screen off), full brightness
    sta         $2100                   ; brightness & screen enable register
    lda         #$00
    sta         $2101                   ; sprite register (size & address in VRAM)
    sta         $2102                   ; sprite registers (address of sprite memory [OAM])
    sta         $2103                   ; sprite registers (address of sprite memory [OAM])
    sta         $2105                   ; graphic mode register
    sta         $2106                   ; mosaic register
    sta         $2107                   ; plane 0 map VRAM location
    sta         $2108                   ; plane 1 map VRAM location
    sta         $2109                   ; plane 2 map VRAM location
    sta         $210A                   ; plane 3 map VRAM location
    sta         $210B                   ; plane 0 & 1 Tile data location
    sta         $210C                   ; plane 2 & 3 Tile data location
    sta         $210D                   ; plane 0 scroll x (first 8 bits)
    sta         $210D                   ; plane 0 scroll x (last 3 bits)
    sta         $210E                   ; plane 0 scroll y (first 8 bits)
    sta         $210E                   ; plane 0 scroll y (last 3 bits)
    sta         $210F                   ; plane 1 scroll x (first 8 bits)
    sta         $210F                   ; plane 1 scroll x (last 3 bits)
    sta         $2110                   ; plane 1 scroll y (first 8 bits)
    sta         $2110                   ; plane 1 scroll y (last 3 bits)
    sta         $2111                   ; plane 2 scroll x (first 8 bits)
    sta         $2111                   ; plane 2 scroll x (last 3 bits)
    sta         $2112                   ; plane 2 scroll y (first 8 bits)
    sta         $2112                   ; plane 2 scroll y (last 3 bits)
    sta         $2113                   ; plane 3 scroll x (first 8 bits)
    sta         $2113                   ; plane 3 scroll x (last 3 bits)
    sta         $2114                   ; plane 3 scroll y (first 8 bits)
    sta         $2114                   ; plane 3 scroll y (last 3 bits)
    lda         #$80                    ; increase VRAM address after writing to $2119
    sta         $2115                   ; VRAM address increment register
    lda         #$00
    sta         $2116                   ; VRAM address low
    sta         $2117                   ; VRAM address high
    sta         $211A                   ; initial mode 7 setting register
    sta         $211B                   ; mode 7 matrix parameter A register (low)
    lda         #$01
    sta         $211B                   ; mode 7 matrix parameter A register (high)
    lda         #$00
    sta         $211C                   ; mode 7 matrix parameter B register (low)
    sta         $211C                   ; mode 7 matrix parameter B register (high)
    sta         $211D                   ; mode 7 matrix parameter C register (low)
    sta         $211D                   ; mode 7 matrix parameter C register (high)
    sta         $211E                   ; mode 7 matrix parameter D register (low)
    lda         #$01
    sta         $211E                   ; mode 7 matrix parameter D register (high)
    lda         #$00
    sta         $211F                   ; mode 7 center position X register (low)
    sta         $211F                   ; mode 7 center position X register (high)
    sta         $2120                   ; mode 7 center position Y register (low)
    sta         $2120                   ; mode 7 center position Y register (high)
    sta         $2121                   ; color number register ($00-$ff)
    sta         $2123                   ; bg1 & bg2 window mask setting register
    sta         $2124                   ; bg3 & bg4 window mask setting register
    sta         $2125                   ; obj & color window mask setting register
    sta         $2126                   ; window 1 left position register
    sta         $2127                   ; window 2 left position register
    sta         $2128                   ; window 3 left position register
    sta         $2129                   ; window 4 left position register
    sta         $212A                   ; bg1, bg2, bg3, bg4 window logic register
    sta         $212B                   ; obj, color window logic register (or, and, xor, xnor)
    sta         $212C                   ; main screen designation (planes, sprites enable)
    sta         $212D                   ; sub screen designation
    sta         $212E                   ; window mask for main screen
    sta         $212F                   ; window mask for sub screen
    lda         #$30
    sta         $2130                   ; color addition & screen addition init setting
    lda         #$00
    sta         $2131                   ; add/sub sub designation for screen, sprite, color
    lda         #$E0
    sta         $2132                   ; color data for addition/subtraction
    lda         #$00
    sta         $2133                   ; screen setting (interlace x,y/enable SFX data)
    sta         $4200                   ; enable v-blank, interrupt, joypad register
    lda         #$FF
    sta         $4201                   ; programmable I/O port
    lda         #$00
    sta         $4202                   ; multiplicand A
    sta         $4203                   ; multiplier B
    sta         $4204                   ; multiplier C
    sta         $4205                   ; multiplicand C
    sta         $4206                   ; divisor B
    sta         $4207                   ; horizontal count timer
    sta         $4208                   ; horizontal count timer MSB
    sta         $4209                   ; vertical count timer
    sta         $420A                   ; vertical count timer MSB
    sta         $420B                   ; general DMA enable (bits 0-7)
    sta         $420C                   ; horizontal DMA (HDMA) enable (bits 0-7)
    sta         $420D                   ; access cycle designation (slow/fast rom)

    ; Zero-initialize VRAM via DMA

    ldx         #$0000
    stx         $2116                   ; Starting address in VRAM
    lda         #%00001001
    sta         $4300                   ; DMA properties: CPU to PPU, no increment, 2 bytes to 2 registers
    lda         #$18
    sta         $4301                   ; Set write address to $2118
    ldx         #.loword(zerobyte)
    stx         $4302                   ; Set source address to the zero byte
    lda         #.bankbyte(zerobyte)
    sta         $4304                   ; Set source address to the zero byte
    ldx         #$0000
    stx         $4305                   ; Writing 0 bytes??
    lda         #%00000001
    sta         $420B                   ; Enable DMA channel 0

    ; Set up tile map for backgrounds

    lda         #$00                    ; Set BG1 tile map at VRAM $0000-07FF, 32x32
    sta         $2107
    lda         #$08                    ; Set BG2 tile map at VRAM $1000-17FF, 32x32
    sta         $2108
    lda         #$41                    ; Set BG1 CHR at $2000 and BG2 CHR at $8000
    sta         $210b

    ; Copy BG1 tile map to VRAM at $0000 via DMA

    lda         #$0000
    stx         $2116
    ldx         #$1801
    stx         $4300
    ldx         #.loword(frame0map)
    stx         $4302
    ldx         #.bankbyte(frame0map)
    sta         $4304
    ldx         #frame0maplen
    stx         $4305
    lda         #$01
    sta         $420B

    ; Copy BG2 tile map to VRAM at $1000 via DMA

    ldx         #$0800
    stx         $2116
    ldx         #$1801
    stx         $4300
    ldx         #.loword(frame1map)
    stx         $4302
    ldx         #.bankbyte(frame1map)
    sta         $4304
    ldx         #frame1maplen
    stx         $4305
    lda         #$01
    sta         $420B

    ; Copy BG1 CHR to VRAM at $2000 via DMA

    ldx         #$1000
    stx         $2116
    ldx         #$1801
    stx         $4300
    ldx         #.loword(frame0chr)
    stx         $4302
    ldx         #.bankbyte(frame0chr)
    sta         $4304
    ldx         #frame0chrlen
    stx         $4305
    lda         #$01
    sta         $420B

    ; Copy BG2 CHR to VRAM at $8000 via DMA

    ldx         #$4000
    stx         $2116
    ldx         #$1801
    stx         $4300
    ldx         #.loword(frame1chr)
    stx         $4302
    ldx         #.bankbyte(frame1chr)
    sta         $4304
    ldx         #frame1chrlen
    stx         $4305
    lda         #$01
    sta         $420B

    ; Copy palette to CGRAM via DMA

    lda         #$00                    ; start at palette entry #0
    sta         $2121
    ldx         #$2200
    stx         $4300
    ldx         #.loword(palette)
    stx         $4302
    lda         #.bankbyte(palette)
    sta         $4304
    ldx         #palettelen
    stx         $4305
    lda         #$01
    sta         $420B

    ; Use graphics mode 1

    lda         #$01
    sta         $2105

    ; Enable interrupts

    cli

    ; Enable BG1

    lda         #showbg1
    sta         bgstatus
    sta         $212c

    ; Enable VBlank interrupt

    lda         #$80
    sta         $4200


    ; Set background color to "white smoke"

    sep         #$20                    ; Set the A register to 8-bit.
    lda         #$DE
    sta         $2122
    lda         #$7B
    sta         $2122

    ; End force blank

    lda         #$0F
    sta         $2100

@Loop:
    wai                                 ; wait for NMI
    jmp         @Loop


Empty_Handler:
    rti


NMI:
    rti
When I run the code I do indeed see something on the screen:

Image

So the SNES is running at least, just looks like the data isn't loaded correctly or something...

I tried to check how they look by opening frame0.chr in YY-CHR and I got this:

Image

It seems to be alright to me, but quick question: how do I import the palette data that superfamiconv generates? I tried using all of these options:

Image

but it doesn't seem to work...

I got Geiger's Snes9x debugger for Windows and opened up my ROM, but I'm not exactly sure what I'm looking for or how to spot it...
Last edited by rchoudhary on Tue Apr 30, 2019 10:45 am, edited 1 time in total.
rchoudhary
Posts: 24
Joined: Wed Apr 10, 2019 4:24 pm

Re: How to show graphics on the screen?

Post by rchoudhary »

HOLY CRAP I GOT IT TO WORK!!!!

There were a couple of typos during the DMA stuff where it would do ldx followed by a sta. Changing those fixed all the graphics! :D

I'm still curious though, how can I load the palette into YY-CHR? And how could I have caught this bug in the debugger? How can I set breakpoints at specific instructions? The breakpoint menu seems to just take raw addresses or something...

Currently working on animating it...
rchoudhary
Posts: 24
Joined: Wed Apr 10, 2019 4:24 pm

Re: How to show graphics on the screen?

Post by rchoudhary »

Ok so I got it to animate, but here is what the second frame looks like this:

Image

Everything's the wrong color!

I had a hunch that the reason is that superfamiconv uses different color indices for the two frames. To test this, I reran the superfamiconv for both frames but I kept the palettes from both and compared them:

Image

While the same stuff is in both files, its in different orders!

There are a couple of ways I figure I could fix this:
  • 1. I could just keep track of 2 palettes and swap them during VBlank, but that seems unnecessary since both frames use exactly the same colors.

    2. The easiest way to fix this would be if I could supply a palette as an input to superfamiconv, but it doesn't look like that's an option...

    3. The second easiest way is if I could open the CHR file in YY-CHR, I could just edit the data so that it's the right colors. However, since I can't open the palette, this would also be kinda difficult...

But there are several other things that are weirdly wrong about the second scene as well. The first thing is that a piece of confetti has a weird aura around it:

Image

The second thing is that the cake frosting has some missing pixels:

Image

And then finally, the last balloon's shine is a different color than the others and the shadow has two different colors in it:

Image

These all seem like things that just might have resulted from errors in my original images, which is fine really. I can just go back and fix it. Worst case is if it turns out to be a bug with superfamiconv then editing the CHR data should suffice.

All in all, it looks like it all comes back to getting YY-CHR to work

UPDATE

The missing pixels from the cake were indeed because they were missing from the initial frame1.png.

However, the confetti aura, the two-tone shadow, and the mis-colored shine must be caused by something else, because in regards to those things, the png file was ok

UPDATE 2

Ok I made some headway in seeing why YY-CHR can't work with the palettes from superfamiconv. Here is a comparison of the .pal file generated by superfamiconv (called palette.pal) and the .pal file saved by YY-CHR (called yy-chr.pal):

Image

So while superfamiconv compresses color data into 2 bytes in the 0bbbbbgg gggrrrrr format, yy-chr saves (and I guess loads) palette data as RGB 888 values.

I was tipped off by the fact that $DE, $7B corresponds to (247, 247, 247) which is #F7F7F7.

Here is a comparison of the native data format generated by superfamiconv using superfamiconv palette -i frame0.png -d palette.dat and the .dat file saved by YY-CHR (called yy-chr.dat):

Image

This one confuses me because the differences between the two files are scattered... I'm not exactly sure why they are the way they are...

But anyways, it seems that if I want to take the palette from superfamiconv, palette.pal, and import it into YY-CHR, what I need to do is take the data and turn it from SNES format to RGB 888 format. A simple python script would do the trick I bet.

Then I can observe the frame1 CHR data using the frame0 palette. Then I can begin recoloring!
Post Reply