Demonstration of translucency

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Demonstration of translucency
by on (#212460)
Well, I was going to make a post asking for help, but I solved the problem right before I would have made it. :D

So, I created translucency in BG mode 1, with 16x16 tiles. BG1 is the main window and BG2 is the subwindow. BG1 tile map is at VRAM $0000, while its tile CHR is at $1000. BG2 tile map is at $0800, and BG2 tile CHR is at $2000. Frame 2 for BG2 tile CHR, so far unused, will be at $3000. I will set up the animation.

One thing I learned on this project is that when you load the tile map, always assume the map is 64x64 even if it's not. (EDIT: Another thing I learned is you must always set up the scroll values, even if your game doesn't scroll, that way they are all set to 0.)

The files, including screenshot and project files, are attached.

Here is the video setup code:

Code:
Setup_Video:
    lda #%00110001
    sta $2105

    stz $2107
    lda #$08
    sta $2108

    lda #$21
    sta $210b

    lda #$01
    sta $212c
    lda #$02
    sta $212d

    lda #$02
    sta $2130
    lda #$41
    sta $2131

    stz $210d
    stz $210e
    stz $210f
    stz $2110

    lda #$0f
    sta $2100
    lda #$80
    sta $4200

    rts
Re: Demonstration of translucency
by on (#212461)
OK, now I've added animation. This is the NMI:

Code:
VBlank:
    inc $10
    lda $10
    cmp #$5a
    bcs Frame2
LoadFrame1:
    lda #$21
    sta $210b
    bra VBlankEnd
Frame2:
    cmp #$b4
    bcs Frame1
    lda #$31
    sta $210b
    bra VBlankEnd
Frame1:
    stz $10
    bra LoadFrame1

VBlankEnd:
    rti


I did "stz $10" in the main vector just before it goes into its infinite loop.
Re: Demonstration of translucency
by on (#212494)
I'm going to assume you're using NMI with VBlank properly (vs. waiting around in a loop polling $4210 bit 7 to detect VBlank), given your other post.

Your NMI vector routine needs an 8-bit read of $4210 at the very start (ex. lda $4210 assuming accum is 8-bit). I'm not sure if this will solve your problem, but it's quite possible (and should be done anyway!).

I would also suggest, given your quote "(EDIT: Another thing I learned is you must always set up the scroll values, even if your game doesn't scroll, that way they are all set to 0.)", is that you actually follow the official register initialisation procedure per the official manual (chapter 26, Register Clear). It's very possible you aren't pre-initialising SNES registers properly (note this, obviously, doesn't pre-zero or pre-initialise RAM contents or anything else -- that's for you to do. I'm talking just about MMIO registers). This should be done in your RESET vector routine.

Also, if you're using bsnes as your test emulator, know that the emulator intentionally pre-initialises register values and RAM with bogus values, which can often lead to problems if your code does not properly initialise or zero things (registers, RAM, etc.) in advance. This has been discussed many times over. It isn't an emulator bug -- if anything, byuu's choice to do this often results in people finding these types of bugs in their program. :-)

(2018/08/29 Edit: attachments removed.)
Re: Demonstration of translucency
by on (#212496)
koitsu,

I appreciate your post, but I had already gotten it working on NO$SNS and Geiger's SNES debugger thingie. The only thing I had neglected, was the $4210 read at the beginning of the NMI (though why this is required is beyond me). It works in bsnes.

So the version with both the .sfc extension (as that is the only extension bsnes will accept) and the .smc extension, are attached.

And yes, it works.
Re: Demonstration of translucency
by on (#212498)
Preface: HARDWARE FOLKS, PLEASE CORRECT ME IF I'M WRONG OR DESCRIBE THIS INACCURATELY. lidnariq, I'm lookin' at you ;-)

beneficii wrote:
... The only thing I had neglected, was the $4210 read at the beginning of the NMI (though why this is required is beyond me). ...

The explanation is semi-complicated and involves understanding a little bit about how the actual hardware works. I understand "part" of how it works, but myself have questions. The NES suffers from the same phenomenon (sort of), however see the very last paragraph of this post for a "hmm, maybe the SNES does something different/unique". I'll provide references for my statements as well.

NMI on the 6502/65c02/65816 is a "edge-sensitive" or "edge-triggered" signal connected via a physical trace between the NMI pin on the CPU and the PPU (I don't remember on the SNES if it's PPU1 or PPU2, but it's irrelevant in understanding the behaviour). The signal is digital, so it only has two states: high (1) and low (0).

NMI is what's known as a "falling edge", which means the signal must transition from high to low (1 --> 0) for the CPU to run code at your NMI vector. Low to low (0 --> 0), high to high (1 --> 1), and low to high (0 --> 1) all do nothing. (Educational: in contrast, "rising edge" would mean the signal must transition from low to high (0 --> 1) for something to happen).

As such, it's presumed the PPU keeps the signal high (1) as a default -- keep reading.

When $4200 bit 7 is set to 1 (the trigger-NMI-on-VBlank bit), when VBlank happens, the PPU sets the signal to low (0). Thus, the high --> low transition causes NMI to happen within the CPU, and it runs code starting at the NMI vector.

Following so far? Maybe a bit complex and into the hardware level of things, but it should make sense.

For the PPU to essentially "reset" back to its default state -- make the PPU keep the signal high (1) again -- one must read register $4210 to essentially "reset the latch".

Failing to read $4210 in your NMI routine would therefore mean the signal would remain in low (0) state perpetually -- which, to me, means no more NMIs would fire (i.e. it happens once and that's it). However, what I've seen stated repeatedly is that failure to do this can cause "NMI to fire multiple times" (I don't understand how that would happen because I'm not a hardware guy).

There are at least two documents that outline NMI behaviour with VBlank on the SNES (I haven't looked at nocash's, but there's probably useful stuff in there too). Here are those:

Charles MacDonald's documentation says the following, emphasis mine:

Quote:
Non-maskable interrupts

According to the 65816 manual, the NMI signal is an edge-sensitive input,
so that only a high to low transition on the NMI pin will cause an interrupt
to occur. Leaving it high, low, or having a low to high transition has no
effect. I'm going to assume the PPU normally holds NMI high and brings it
low to trigger an interrupt.

The PPU will pull NMI low at line $00E8 in 224-line mode or line $00F7 in
239-line mode. It will remain low until either $4210 is read, or a new
frame starts, at which point the PPU will bring NMI high again.


The inverted state of the NMI signal from the PPU can be read through bit 7
of $4210, where 0= no NMI has been requested, 1= an NMI is pending. After
reading this register, the PPU resets the NMI signal by pulling it high.

Bit 7 of $4200 doesn't affect how the PPU manages NMIs at all, it is just
a gate between the NMI output of the PPU and the NMI input of the CPU.
When set, NMIs can be generated. When cleared, the CPU ignores the state of
the NMI pin.

Here are some details of specific situations:

When there is a pending NMI, toggling bit 7 of $4200 does not create any
additional interrupts.

Bit 7 of $4210 will be set in the NMI routine when it executes.

After the NMI routine finishes, bit 7 will remain set (assuming $4210 wasn't
read) until the start of the next frame. This will not cause more NMIs to
occur as they are only triggered by high-to-low transitions, having the NMI
pin remain low does not do anything.


Bit 7 of $4210 reflects the NMI status independantly of bit 7 of $4200.

Anomie's SNES timing doc says this, emphasis mine:

Quote:
The internal timer will set its NMI output low at H=0.5 at the beginning of
V-Blank. The CPU's /NMI input is forced high by clearing bit 7 of register
$4200, so the CPU may not actually see the NMI transition. The CPU will jump to
the NMI routine at the end of the instruction during which /NMI transitions.

The internal timer sets its NMI output high at H=0 V=0, or when register
$4210 is read.
Possibly also when $4200 is written?

So if the PPU itself is resetting NMI to high (1) when it begins to draw the pixel at 0,0 then why do Nintendo's docs say one must read $4210 else risk duplicate NMIs -- and how would you risk getting more than one?

Both can potentially be explained by details about the NES.

The nesdev Wiki, and this is for the NES specifically, has a write-up about this behaviour, specifically how NMI can end up occurring multiple times. PPUCTRL ($2000) in that Wiki page would be essentially $4200 on the SNES, and PPUSTATUS ($2002) would essentially be $4210 on the SNES. On the NES, you need to read $2002 and write to $2000 to "truly" get the PPU to re-set the NMI signal back to high (1). On the NES, I don't think when H=0 and V=0 does the NES PPU set NMI to high (1) automatically.

So I'm left to believe that the comments in the official SNES documentation are a kind of "learned behaviour" from the NES days, where you had to "do extra stuff" to essentially get the PPU to reset the NMI signal to high (1). Nintendo may have alleviated all that nonsense by simply having the PPU set NMI to high (1) itself when drawing pixel 0,0.

Long-winded answer, but it's the best I got. If someone else has more concrete details or explanations, I'd love to hear 'em.

Edit: it looks like nocash's documentation sort of confirms my suspicions, but actually explains how "multiple NMIs" could happen if you don't read $4210. From http://problemkaputt.de/fullsnes.htm#snesppuinterrupts talking about $4210, emphasis mine:
Quote:
The SNES has only one NMI source (vblank), and the NMI flag is automatically reset (on vblank end), so there's normally no need to read/acknowledge the flag, except one special case: If one does disable and re-enable NMIs, then an old NMI may be executed again; acknowledging avoids that effect.

The CPU includes another internal NMI flag, which gets set when "[4200h].7 AND [4210h].7" changes from 0-to-1, and gets cleared when the NMI gets executed (which should happen around after the next opcode) (if a DMA transfer is in progress, then it is somewhere after the DMA, in that case the NMI can get executed outside of the Vblank period, ie. at a time when [4210h].7 is no longer set).

So yeah, it looks like Nintendo alleviated the annoyance that stemmed from the NES days by doing things differently, but also added that you should read from $4210 for a particular unique case (if you were to set bit 7 of $4200 to 0, then later set bit 7 of $4200 to 1, without reading $4210 in your NMI routine, you could risk re-executing your NMI routine).

Hardware is a weird beast.
Re: Demonstration of translucency
by on (#212521)
koitsu wrote:
So yeah, it looks like Nintendo alleviated the annoyance that stemmed from the NES days by doing things differently, but also added that you should read from $4210 for a particular unique case (if you were to set bit 7 of $4200 to 0, then later set bit 7 of $4200 to 1, without reading $4210 in your NMI routine, you could risk re-executing your NMI routine).
It's actually the same in both the SNES and NES.

In the NES, during vblank, if you haven't read $2002 and you write 0 then $80 to $2000, you'll get another NMI.
In the SNES, during vblank, if you haven't read $4210 and you write 0 then $80 to $4200, you'll get another NMI.

The more likely version of both of these flaws is "NMI is disabled, you enable NMI during vblank, you immediately get an NMI that may not have enough time to do a full upload"

In the NES, the NMI flag is cleared at the same time as everything else (the sprite overflow, sprite 0 hit, and the "you can't usefully talk to the PPU right after reset" behavior), which I think is coordinate (0,-1)