From 98bad9d6d27d2cf9cfef2d14d302d03bcd08c9c6 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Tue, 18 Sep 2018 00:59:39 -0400 Subject: [PATCH] Pattern, name, and attribute tables This commit shows how to load pattern, name, and attribute tables, and explains their roles in rendering graphics. A simple pattern table with text characters as well as some multi-pattern graphics is shown. --- Makefile | 4 +- README.md | 14 ++-- src/background.asm | 178 ++++++++++++++++++++++++++++++++++++++++++++ src/bgpalette.asm | 140 ---------------------------------- src/generitiles.pat | Bin 0 -> 3072 bytes 5 files changed, 189 insertions(+), 147 deletions(-) create mode 100644 src/background.asm delete mode 100644 src/bgpalette.asm create mode 100644 src/generitiles.pat diff --git a/Makefile b/Makefile index aa81aa7..a8a75ea 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ SRCDIR = src OBJDIR = obj BINDIR = bin -TARGET = bgpalette.nes +TARGET = background.nes SOURCES := $(wildcard $(SRCDIR)/*.asm) OBJECTS := $(SOURCES:$(SRCDIR)/%.asm=$(OBJDIR)/%.o) @@ -23,5 +23,5 @@ $(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.asm .PHONY: clean clean: - @$(rm) $(BINDIR)/$(TARGET) + @$(rm) $(BINDIR)/* @$(rm) $(OBJECTS) diff --git a/README.md b/README.md index 3ac5083..f8332f3 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,19 @@ These are developed on KDE Neon (Linux), and the Makefile may not work for Windows. You will need a copy of [cc65](https://github.com/cc65/cc65) built and -installed. My preferred emulator/debugger is -[mednafen](https://mednafen.github.io/releases/), which on Ubuntu systems can be -installed with a simple `sudo apt-get install mednafen`. +installed. My preferred emulator/debugger is [fceux](http://www.fceux.com) under +Wine, and **NOT** the one under Linux, as their Windows release has a powerful +set of debugging tools available. With them, you can inspect pattern and name tables! + +Alternatively, I'd recommend [mednafen](https://mednafen.github.io/releases/), +which on Ubuntu systems can be installed with a simple `sudo apt-get install mednafen`. My latest example code will always live on the master branch. To see earlier demos/labs, please look at the labXX branches. Current ones include: -Lab 1: Hello World! -Lab 2: Drawing a background color +* Lab 1: Hello World! +* Lab 2: Drawing a background color +* Lab 3: Pattern, name, and attribute tables To build and run: ``` diff --git a/src/background.asm b/src/background.asm new file mode 100644 index 0000000..bf6afc3 --- /dev/null +++ b/src/background.asm @@ -0,0 +1,178 @@ +; Nametables and attribute tables Demo for NES +; Following on from the background palette program, this program looks at how +; to load a pattern table, which contains the actual graphic tiles, a nametable, +; which contains the layout of graphic tiles for the background, and the +; attribute table, which specifies what palette to paint everything with. +; +; I am not an artist, and even then, converting bitmaps to pattern tables can be +; a chore. So, I used the "Generitiles" from the NESDev wiki +; (https://wiki.nesdev.com/w/index.php/Placeholder_graphics). I then ran these +; through a Python script supplied by Damian Yerrick +; (https://github.com/pinobatch/nesbgeditor) which converts a 2-bit PNG into +; pattern tables pretty darn effectively. Thanks, Damian! +; +; So, we're now *finally* using the CHARS section, which gets directly mapped +; into the PPU's memory at power on. This section really should more +; be named "CHR-ROM", as this is the more common name for it. You'll notice +; that I can directly include the file produced by Damian's tools, which keeps +; the code tidy. +; +; With the patterns directly mapped in, the next step is to load up some data +; in the name table. Since we're not doing anything fancy, I've restricted +; things to the first of the two name tables. Much like we did for loading the +; palette colors, we load this through the use of PPUADDR and PPUDATA. +; +; Finally, we load the attribute table, which says which palette to use for +; each 32x32 pixel region on the screen. In a more advanced demo, this would +; raise the number of effective colors I was using on the screen. For now, +; though, I just want to keep things simple and easy-to-explain. +; +; Note that the annyoing "hello world" sound is now gone. The graphics show +; that everything is working. + +.define PPUMASK $2001 +.define PPUSTATUS $2002 +.define PPUADDR $2006 +.define PPUSCROLL $2005 +.define PPUDATA $2007 + +.define NAMETABLE_0_HI $20 +.define NAMETABLE_0_LO $00 +.define ATTRTABLE_0_HI $23 +.define ATTRTABLE_0_LO $C0 +.define BGPALETTE_HI $3F +.define BGPALETTE_LO $00 + +; Mandatory iNES header. +.segment "HEADER" + +.byte "NES", $1A ; "NES" magic value +.byte 2 ; number of 16KB code pages (we don't need 2, but nes.cfg declares 2) +.byte 1 ; number of 8KB "char" data pages +.byte $00 ; "mapper" and bank-switching type (0 for "none") +.byte $00 ; background mirroring flats + ; + ; Note the header is 16 bytes but the nes.cfg will zero-pad for us. + +; code ROM segment +; all code and on-ROM program data goes here + +.segment "STARTUP" + +; reset vector +reset: + bit PPUSTATUS ; clear the VBL flag if it was set at reset time +vwait1: + bit PPUSTATUS + bpl vwait1 ; at this point, about 27384 cycles have passed +vwait2: + bit PPUSTATUS + bpl vwait2 ; at this point, about 57165 cycles have passed + + ; Interesting little fact I learned along the way. Because it takes two + ; stores on PPUADDR to move its pointer, it's good practice to start all of + ; your PPUADDR use with a peek at PPUSTATUS since this resets its "latch" + ; and ensures you're addressing the address you expect. + ; Technically, we don't need this because we did it in the reset code, but + ; it's a neat little thing to mention here + + bit PPUSTATUS + + ; load the background palette + lda #BGPALETTE_HI + sta PPUADDR + lda #BGPALETTE_LO + sta PPUADDR + + ; prep the loop + ldx #0 + +paletteloop: + lda bgpalette, X ; load from the bgpalette array + sta PPUDATA ; store in PPUDATA, PPU will auto-increment + inx ; increment the X (index) register + cpx #16 + bne paletteloop ; run the loop until X=16 (size of the palettes) + +; move PPUADDR over to nametable 0. + lda #NAMETABLE_0_HI + sta PPUADDR + lda #NAMETABLE_0_LO + sta PPUADDR + +; This loop iterates over the pattern table, outputting it in lines of 16 +; The other 16 are just padded out with a pattern that's blank. This lets +; me easily show you some simple graphics that are made up of multiple +; stacked tiles without getting too fancy. In reality, you'd probably have +; complete nametables that you'd load in from files and simply run in a loop. + + ldx #$00 + ldy #16 +nametableloop: + stx PPUDATA + inx + dey + bne nametableloop + ldy #16 +padright: + sta PPUDATA + dey + bne padright + cpx $D0 + beq done + ldy #16 + jmp nametableloop +done: + +; set up Palette 0 for everything + bit PPUSTATUS + lda #ATTRTABLE_0_HI + sta PPUADDR + lda #ATTRTABLE_0_LO + sta PPUADDR + ldx #64 ; 64 tiles in the attribute table + lda #0 + +attrloop: + sta PPUDATA + dex + bne attrloop + +; Enable background and sprite rendering. This is suuuuuper important to +; remember. I didn't remember to put this in and probably blew a whole day +; trying to figure out why my emulator hated me. + lda #$1e + sta PPUMASK + +forever: + jmp forever + +nmi: + rti ; Return from the NMI (NTSC refresh interrupt) + + +; The background colors are, in order: +; $0F: black +; $15: red +; $22: blue +; $20: white + +bgpalette: + .byte $0F, $15, $22, $20 ; palette 0 + .byte $0F, $15, $22, $20 ; palette 1 + .byte $0F, $15, $22, $20 ; palette 2 + .byte $0F, $15, $22, $20 ; palette 3 + + +; vectors declaration +.segment "VECTORS" +.word nmi +.word reset +.word 0 + +; As mentioned above, this is the place where you put your pattern table data +; so that it can automatically be mapped into the PPU's memory at $0000-$1FFF. +; Note the use of .incbin so I can just import a binary file. Neato! + +.segment "CHARS" +.incbin "generitiles.pat" diff --git a/src/bgpalette.asm b/src/bgpalette.asm deleted file mode 100644 index aae3597..0000000 --- a/src/bgpalette.asm +++ /dev/null @@ -1,140 +0,0 @@ -; Background Palette Loader Demo For NES -; -; Following on from the hello world program, this program expands out into -; interactions with the NES PPU (Picture Processing Unit). This does the bare -; minimum to draw the screen-- it disables all sprites and background graphics -; and fills the screen with the default color. -; -; Doing this requires some extra work. Firsst off, the PPU is not immediately -; ready at power on. Before interacting with it, you need to wait enough cycles -; for it to become ready. There is a standard wait process employed in the -; reset vector. -; -; Loading information into the PPU involves poking specific memory addresses. -; On the NEW, the PPU and CPU are basically independent of each other and they -; each have their own RAM space (the PPU's is called VRAM). Loading data into -; VRAM is done by setting the address by writing it, one byte at a time, to the -; PPUADDR ($2006) register. After that, VRAM can be read and written to using -; lda or sta instructions from the PPUDATA ($2007) register. -; -; The PPU stores 4 different background palettes; each stores 3 colors. A 4th -; color, the default background color, is stored at VRAM $3F00 and aliased at -; $3F04, $3F08, and $3F0C. (They're also aliased at $3F10, $3F14, $3F18, and -; $3F1C). Because any write to those aliases can change the background color, -; it's easier to treat the palette as containing FOUR colors, with the first -; one always being the same. -; -; In this demo, all the palettes are the same. It's actually one of the -; background palettes from Super Mario Brothers - - -.define PPUMASK $2001 -.define PPUSTATUS $2002 -.define PPUADDR $2006 -.define PPUDATA $2007 - -.define BGPALETTE_HI $3F -.define BGPALETTE_LO $00 - -; Mandatory iNES header. -.segment "HEADER" - -.byte "NES", $1A ; "NES" magic value -.byte 2 ; number of 16KB code pages (we don't need 2, but nes.cfg declares 2) -.byte 1 ; number of 8KB "char" data pages -.byte $00 ; "mapper" and bank-switching type (0 for "none") -.byte $00 ; background mirroring flats - ; - ; Note the header is 16 bytes but the nes.cfg will zero-pad for us. - -; code ROM segment -; all code and on-ROM program data goes here - -.segment "STARTUP" - -; reset vector -reset: - bit PPUSTATUS ; clear the VBL flag if it was set at reset time -vwait1: - bit PPUSTATUS - bpl vwait1 ; at this point, about 27384 cycles have passed -vwait2: - bit PPUSTATUS - bpl vwait2 ; at this point, about 57165 cycles have passed - - lda #$00 ; color image, no sprites, no background - sta PPUMASK - - ; request access to the background palette memory. This starts at VRAM - ; address $3F00. VRAM is in a separate address space from RAM, so we access - ; it via the use of PPUADDR and PPUDATA. PPUADDR sets the address to access - ; while PPUDATA is the data, one byte at a time. When you access VRAM via - ; PPUDATA, it auto-increments the address by default, making loops easier. - - ; Start at VRAM address $3F00 - lda #BGPALETTE_HI - sta PPUADDR - lda #BGPALETTE_LO - sta PPUADDR - - ; prep the loop - ldx #0 - - ; load the background palette -paletteloop: - lda bgpalette, X ; load from the bgpalette array - sta PPUDATA ; store in PPUDATA, PPU will auto-increment - inx ; increment the X (index) register - cpx #16 - bne paletteloop ; run the loop until X=16 (size of the palettes) - - - ; The "hello world" sound from the previous exercise, so we know we made - ; it to the end of the reset vector. - lda #$01 ; square 1 - sta $4015 - lda #$08 ; period low - sta $4002 - lda #$02 ; period high - sta $4003 - lda #$bf ; volume - sta $4000 -forever: - jmp forever - -nmi: - rti ; Return from the NMI (NTSC refresh interrupt) - - -; The background colors are, in order: -; $22: pale blue -; $29: drab green -; $1A: forest green -; $0F: black -; Actual color fidelity varies based on emulator and monitor, but -; this gives a rough sense of the colors -; -; Also note that my practical experience shows me that you *must* -; load up all 4 palettes in order to get the PPU to be happy and -; draw your default color to the screen! - -bgpalette: - .byte $22, $29, $1A, $0F ; palette 0 - .byte $22, $29, $1A, $0F ; palette 1 - .byte $22, $29, $1A, $0F ; palette 2 - .byte $22, $29, $1A, $0F ; palette 3 - - -; vectors declaration -.segment "VECTORS" -.word nmi -.word reset -.word 0 - -; The "hello world" program identified this as a space for ROM data. That is -; technically true, but it misses a verey critical nuance. This segment is for -; CHR-ROM, which is a space in ROM which the PPU directly loads into its memory -; for use in rendering the screen! This is not the rodata section! Your -; read-only data used in your program belongs in the STARTUP segment! - -.segment "CHARS" diff --git a/src/generitiles.pat b/src/generitiles.pat new file mode 100644 index 0000000000000000000000000000000000000000..5b904dcb1cc36f45b0b3c86dbd311f6784ccae0a GIT binary patch literal 3072 zcmd6pQD_`h6o${bo6QQFod{A#y1IjuLJ@VI+C`U4Y+r1t?c$4w4_T>QEUYAG(M5*c zErq-Y?OR0RQc z?z!jObI!eUvqX1Fxmq<#?KF8`0He=qb@xh|B$riMk{b=K5mt_|@Sjv7L$W zWnNFSI}|SGqnYGsrumv&2gKdZ8*Y7)^^~hobOZUwlo)kcPuXs_8)yJ&MwM!P&O<*_ zX7}rUy4mQd21JwJRS#;)yZzl_PxdgJP>9hQ@4qI-r9gd}Ma-7VA7OtI@^~Z?JF~ji zcKN((67?{>hzSXkxH63|aWdlru*G7$^U~E6LXP|q&rcZ7M!z(PuCK1HO5~@>`LnLc zJDlW~5aWCR*d8;^tC;2oj@AU5x9H*k#Qrf&ledf4Pe{De=*M<;u>Y*5M1CFCH9Pp$ zSl3MZ!(n)#2nQg7T@$YlAyFcvseV8Gl6t284@Kc+TE8@LmwxK&Q=gvxPEpV8ophU| zGJ3N)bF?|rY z)BLEgw5zxerQ{2R5?7QTA2$qe+OnEWa3F-|ff=!1`*!}&4@7l$c=38SU$!ruJ#*%4 z-f&d7xv2(TeR&N+!vASB8ht@jeeSL1^fWlthrsXf`Q4QYc=8tHu6ncm$kh9w^?x?= z$`0rGcXIDp6ML+W59I#1)fokQ_YZa#maM{Xo=Q~874k%eF+L7Z^RmCt5=5RS!~pu! zmE$iziS|6P^GJMrx;zLO#80BLXO{hjk*9F;ZwDY*tV{*ofEr=w3(T=5OND%n$f+6Q zkAZxfaNvKhU0#?54d;V5R+L&X?APC4RD(r7JaXuuQV0F=_vbID+y(#KIo~*O!Z2oM z4S!?9&+4~6U;6XdFK@oBA{F89o2-r$9%)%w;p$h^;v!eSsAgtnQ2jymij7x0!ZiwWR?mADi>mVe>S#{wT~a8Yd+g=$NsH_Uzzt2!@zBTE>z1@ z;rj=l9uck{%OY=%EZcCZL^a1DYS|`!>Uq9DN5e2El%YT}{#M*P++7xeP>t+*Z70!q z&Dm$Q4&(*?SMc{If&i-w1WmVUwP*2t_GeqI)-)la;P|7!|3CiYM15{Sk?+TP0C%)O a&>RSf{rk>Q_~Q~q{=QG7B0M<%`^bL=R>36z literal 0 HcmV?d00001 -- 2.34.1