From 3e7549c71f60ee1c4b49a7c09fde3b6017818089 Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Tue, 4 Feb 2025 15:46:54 +0100 Subject: Ensure background/sprite tiles are spread properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At least on the MMC3 chip, it's important to have background tiles on the first pattern table and then sprite tiles on the second pattern table. This avoids a hardware bug on scanline IRQs. Signed-off-by: Miquel Sabaté Solà --- README.md | 2 +- assets/diskun.chr | Bin 8192 -> 8192 bytes basics/flicker.s | 2 +- fx/blink.s | 56 +++++++++++++++++++++++++++++++++++++++--------------- scroll/roulette.s | 17 +++++++---------- 5 files changed, 50 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 42302a5..50bb8dc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ check out [this list](#other-projects). You can build everything by just calling `make` and binaries will then be available in the `out` directory. Before doing that, though, you will need a -compiler for the 6052 platform. A good option is +compiler for the 6502 platform. A good option is [cc65](https://github.com/cc65/cc65), which is available on all major platforms, and if you are feeling adventurous you can check out [nasm](https://github.com/mssola/tools.nes). By default the Makefile uses `cc65` diff --git a/assets/diskun.chr b/assets/diskun.chr index 3ed0dc4..fe48e96 100644 Binary files a/assets/diskun.chr and b/assets/diskun.chr differ diff --git a/basics/flicker.s b/basics/flicker.s index 25f1d33..234c8a1 100644 --- a/basics/flicker.s +++ b/basics/flicker.s @@ -27,7 +27,7 @@ jsr init_sprites cli - lda #%10010000 + lda #%10001000 sta $2000 ; PPUCTRL lda #%00011110 sta $2001 ; PPUMASK diff --git a/fx/blink.s b/fx/blink.s index adf8553..4fdaa6b 100644 --- a/fx/blink.s +++ b/fx/blink.s @@ -183,8 +183,9 @@ reset: ;; span 4 banks (that is, the first pattern table on the PPU). We have to ;; account for that when writing into the first two registers (and that's ;; why they are set to 0 and 2 respectively). Other than that, R2-R5 hold - ;; which bank goes into the other remaining regions which are 1KB each. - ;; Because of this, we can simply assign one 1KB bank to each register. + ;; which bank goes into the other remaining regions which are 1KB each + ;; (hence, the second pattern table is controlled via R2-R5). Because of + ;; this, we can simply assign one 1KB bank to each register. ;; ;; To sum things up, on this setup the first pattern table is equally ;; divided by 2 and it's controlled by R0 and R1 respectively. The second @@ -246,12 +247,28 @@ reset: ;;; NOTE: mainly as usual except that a bit of game loop has been added to ;;; handle the blinking state. .proc main + ;; The code will iterate between banks 4 and 6 on the pattern table, as + ;; that's where sprites are located (check the CHARS segment for more info). + lda #4 + sta Vars::last_bank + jsr Diskun::init_palettes jsr init_sprites cli - lda #%10010000 + + ;; NOTE: This looks like other examples, but here having background tiles on + ;; the first pattern table and sprite tiles on the second pattern table is + ;; not a matter of personal taste, but there are technical reasons for it. + ;; If you check how IRQs work on the MMC3 chip: + ;; https://www.nesdev.org/wiki/MMC3#IRQ_Specifics; you will find that on 8x8 + ;; tile mode having this arrangement actually spares us from a hardware bug. + ;; Long story short, placing background tiles first and sprite tiles second + ;; make scanline IRQs reliable. Hence, at least on all MMC3 examples, this + ;; will be guaranteed. + lda #%10001000 sta $2000 + lda #%00011110 sta $2001 @@ -277,17 +294,19 @@ reset: lda #0 sta Vars::counter - ;; The whole trick is done on R0. So select it. - lda #0 + ;; The whole trick is done on R2, which points to $1000, where the sprite + ;; tiles begin. Hence, select it. + lda #2 sta MMC3::BANK_SELECT - ;; The value for the register is either 0 or 2 depending on its last value. + ;; The value for the register is either 4 or 6 depending on its last value. lda Vars::last_bank + cmp #4 beq :+ - lda #0 - beq @set + lda #4 + jmp @set : - lda #2 + lda #6 @set: ;; Save which is the bank being used both internally and onto the MMC3 chip. sta Vars::last_bank @@ -369,14 +388,21 @@ irq: ;;; too much for this example, but it's a reasonable size for an MMC3 game. ;;; Considering only one pair of pattern tables are available at any given ;;; moment (8KB), this means that we need space for 128 / 8 = 16 pairs of -;;; pattern tables here. For this example the first 8KB are realy only used, and -;;; the rest are left with a default value ($00). +;;; pattern tables here. For this example the first 8KB are really only used, +;;; and the rest are left with a default value ($00). .segment "CHARS" -;; First 8KB (note that diskun0.chr and diskun1.chr are both 2KB long) -.incbin "../assets/diskun0.chr" ; First half of the first pattern table has the default diskun character. -.incbin "../assets/diskun1.chr" ; Second half of the first pattern table simply has the blinking version. -.res $1000, $00 ; Second pattern table is left with the default value. +;; As explained when initializing the PPUCTRL register ($2000), it's actually +;; important to place background tiles first and sprite tiles second on the +;; MMC3. This is guaranteed here by setting the first pattern table as empty (we +;; have no background on this example, really). The second pattern table is then +;; filled with `diskun0.chr` and `diskun1.chr`, which are 2KB each. Hence, the +;; 4th CHR bank contains the regular character, and the 6th CHR bank contains +;; the blinking version. You can see these values used when initializing +;; `Vars::last_bank`, or when performing bank switching. +.res $1000, $00 +.incbin "../assets/diskun0.chr" ; First half of the second pattern table has the default diskun character. +.incbin "../assets/diskun1.chr" ; Second half of the second pattern table simply has the blinking version. ;; The 15 other 8KB portions are left empty. .res $2000, $00 diff --git a/scroll/roulette.s b/scroll/roulette.s index ecf2569..4b13c22 100644 --- a/scroll/roulette.s +++ b/scroll/roulette.s @@ -1,6 +1,6 @@ ;;; ;; Divide the screen in three rows and make them move in different -;; directions/speed. This is achieved thanks to the MMC3 chip (check the +;; directions/speed. This is achieved thanks to the MMC3 chip (check ;; `fx/blink.s` for further information on this chip). In particular, we are ;; using the scanline IRQ mechanism provided by this chip to react to different ;; parts of the screen being rendered: @@ -208,7 +208,7 @@ reset: ;; NOTE: enable back interrupts so we can set them up later on `nmi` code. cli - lda #%10010000 + lda #%10001000 sta $2000 lda #%00011110 sta $2001 @@ -386,9 +386,9 @@ nmi: ldx #$00 stx MMC3::IRQ_DISABLE - ;; The screen is made up of 240 scan lines. Since we are dividing the screen - ;; by 3: 240 / 3 = 80. Hence, the next IRQ should happen on scanline 80, - ;; where we would need to update the scroll value through the + ;; The screen is made up of 240 visible scan lines. Since we are dividing + ;; the screen by 3: 240 / 3 = 80. Hence, the next IRQ should happen on + ;; scanline 80, where we would need to update the scroll value through the ;; `{center/bottom}_scroll` values instead. Moreover, note that ;; `MMC3::IRQ_ENABLE` accepts any value, so the same value as the two other ;; registers is just fine. @@ -493,15 +493,12 @@ irq: rti -;;; NOTE: pretty much the same as `fx/blink.s` but we also copy the same data on -;;; the second pattern table as that's the one being used for background -;;; elements. +;;; NOTE: pretty much the same as `fx/blink.s`. .segment "CHARS" .incbin "../assets/diskun0.chr" .incbin "../assets/diskun1.chr" -.incbin "../assets/diskun0.chr" -.incbin "../assets/diskun1.chr" +.res $1000, $00 ;; The 15 other 8KB portions are left empty. .res $2000, $00 -- cgit v1.2.3