Page 1 of 3
[My emulator] Graphics glitches - SuperMarioBros
Posted: Sat Sep 24, 2011 4:34 am
by L-proger
Hi all!
Sorry, if my English isn't good : )
I'm newbie in emulation! Seven days ago i started to write my first emulator (of course NES ^__^). I had no imagination about how to write it or how it should works : ) Anyway, after 7 days (now) i can run few simple games, like SuperMarioBrothers, Excitebike, IceClimber and few more with 0 mapper.
Before implementing various mappers i want to fix all emu glitches. look at screen below:
This is the only one GFX bug in Mario, everything else looks really good. Second day i can't figure out why some parts of background in startup menu are missing and few of them are replaced with "red cloud" sprites : \
First thing i thought is that i did something wrong in background rendering code! I checked it 10 times - everything looks fine.
Then i used FCEUX to find one my corrupted tile address in VRAM and tiles ID in PatternTable!
That tile:
Here should be renderer tile with ID == 0x46 but instead rendered tile 0x24 (transparent) o_O
FCEUX says, that this tile ID is placed in 0x21A5 VRAM address (NameTable0). I made a few hooks to watch - what and when is writing at that address - and.. surprise! Game exactly writes value 0x24 (transparent)!!! It means, that i render everything correcly but game writes wrong values in NameTable0!
And question is: "why?" I think, that this is some kind of GFX debugging, implemented by Mario game programmers ) It's just a reaction on something made wrong. So, maybe somebody know what exactly can allow game to produce such glitches?
Big thanx to all! : )
Posted: Sat Sep 24, 2011 5:22 am
by Memblers
SMB is reading some data from CHR-ROM there, isn't it? Most games don't do that. That might be where the problem is.
Posted: Sat Sep 24, 2011 5:40 am
by L-proger
Thanks for reply!
Yep, SMB is reading something from CHR-ROM, but reading is already implemented! : \
Maybe that can help: i tried to run CPU/PPU test ROMs on my EMU, but i can't see any sprites rendered! (only black screen). I tried so many tricks, so i don't have ideas now on what is wrong with emu. Hmm.
(i implemented CHR-RAM writes too, so i expected that test ROMs would work fine, but they don't)
Posted: Sat Sep 24, 2011 6:20 am
by tepples
Are you making sure that data read back from CHR ROM is delayed by one read? For example, point the VRAM address at $1F00, where $1F00 contains "HELLO WORLD",0, and reading $2007 will read back a byte of garbage before "HELLO".
Posted: Sat Sep 24, 2011 7:12 am
by L-proger
tepples wrote:Are you making sure that data read back from CHR ROM is delayed by one read? For example, point the VRAM address at $1F00, where $1F00 contains "HELLO WORLD",0, and reading $2007 will read back a byte of garbage before "HELLO".
OMG! O____O Just fixed! Thanx!!!
Look what i did:
Code: Select all
uint8 PPU::ReadVRAM()
{
uint8 val = 0;
if(reg_vramAddress2 < 0x2000) //pattern table
{
val = pNes->pCart->ReadChrRom(reg_vramAddress2 - 1);
}
else if ....
//blah blah blah
reg_vramAddress2 += addressIncrement;
return val;
}
I added "-1" to read address! Please, explain - why i should do this? I can't catch..
If i understand correctly:
To write some value to some address of VRAM, game should:
1. Write VRAM address in PPU "VRAM Address Register 2" - 2 writes by 8 bits, then combine them to 16 bit address;
2. Well, write data and then increment pointer by 1 or 32 bytes (depends on 0x4 bit of "PPU Control Register 1".
And same thing to read data! Specify pointer to data and read it... hmm
What's wrong? What I'm missing? : )
Posted: Sat Sep 24, 2011 7:24 am
by 3gengames
Add a read buffer that stuff go directly to the CPU when you read, as when you reset the address, the buffer to read gets filled with I believe the last write to the PPU. See the bottom of the wiki:
http://wiki.nesdev.com/w/index.php/PPU_registers
I'm not sure what the internal buffer is, my guess is the last write, but let somebody else who really knows answer that. And also, keep in mind it's not for the palette range, those are directly read back, no buffer.
Posted: Sat Sep 24, 2011 7:32 am
by L-proger
>I'm not sure what the internal buffer is
this is just a 1 byte value of previous read operation in address range 0-$3EFF, as i understand!
I just made correct delayed read, not just "-1". But i want to understand, why not read it immediately? Why CPU needs that delay? And why then reading of $3F00-$3FFF should not be delayed??
btw - cool, now i understand why documents says, that first read of VRAM would return garbage =)
Posted: Sat Sep 24, 2011 8:32 am
by tokumaru
L-proger wrote:But i want to understand, why not read it immediately? Why CPU needs that delay? And why then reading of $3F00-$3FFF should not be delayed??
Most of us don't know about the hardware details, so I'm not sure you'll get a definitive answer to this. What we DO know though is that the pattern and name tables, which are affected by the delay, are memory chips separate from the PPU chip, but the palette RAM is internal to the PPU, so it doesn't have to make any external memory accesses to fetch data from it.
Posted: Sat Sep 24, 2011 8:35 am
by L-proger
tokumaru wrote:L-proger wrote:But i want to understand, why not read it immediately? Why CPU needs that delay? And why then reading of $3F00-$3FFF should not be delayed??
Most of us don't know about the hardware details, so I'm not sure you'll get a definitive answer to this. What we DO know though is that the pattern and name tables, which are affected by the delay, are memory chips separate from the PPU chip, but the palette RAM is internal to the PPU, so it doesn't have to make any external memory accesses to fetch data from it.
Okay, i got it : )
Big thanks to all!! Many games now works correctly ^__^
Posted: Sat Sep 24, 2011 9:28 am
by tepples
It's probably because the PPU can't complete the read within the time that the CPU expects, so it reads to a buffer and then returns that buffer on the next read. On the Apple IIGS, the interface to wave RAM has the same delayed-read behavior.
Is it documented what mixing reads and writes leaves in the buffer? Or what mixing $0000-$3EFF reads and palette ($3F00-$3FFF) reads leaves? Or what rendering leaves?
Posted: Sat Sep 24, 2011 9:39 am
by tokumaru
tepples wrote:Is it documented what mixing reads and writes leaves in the buffer? Or what mixing $0000-$3EFF reads and palette ($3F00-$3FFF) reads leaves? Or what rendering leaves?
I'm a bit curious about those behaviors myself. They should be easy to test though, except for the rendering part...
Posted: Sat Sep 24, 2011 7:33 pm
by Zelex
I'm always amazed at some people here's ability to look at a picture and know precisely what is wrong at the code level. Dude, fraking amazing
Posted: Mon Sep 26, 2011 9:38 am
by kevtris
tepples wrote:It's probably because the PPU can't complete the read within the time that the CPU expects, so it reads to a buffer and then returns that buffer on the next read. On the Apple IIGS, the interface to wave RAM has the same delayed-read behavior.
Is it documented what mixing reads and writes leaves in the buffer? Or what mixing $0000-$3EFF reads and palette ($3F00-$3FFF) reads leaves? Or what rendering leaves?
I am not 100% sure what happens on mixed read/write, but the pointer is incremented after each. You can use reads to skip bytes to be written in chr space.
I tested this ages ago and I cannot remember if the buffer (which stores the read byte) is used or not to store the written value.
The reason reads are buffered is as described though: the chip has to perform a read, and it takes longer to do this than the CPU can wait, so you get to read the previous buffer value instead. The exception to this is the palette: Since it's on-chip, there is no delay. You read it back immediately.
Once you read the buffer, the PPU then fetches the next byte from CHR space.
As for reading memory UNDER the palette registers, you can do this thanks to the buffer. Reading from 3fxx will return palette data, and it will also read from 3fxx in PPU space (usually the nametables since they are mirrored from 2000-3fff). This can be used to read one byte at a time from external space at 3fxx.
To do this, you first read from i.e. 3f00 which returns the first palette entry, then you read from somewhere else (i.e. 1000h or 0000h, basically anywhere except 3f00). this will return the byte of data in the buffer, which was loaded from 3f00.
Then you read from 3f01 to load the buffer, then read from i.e. 1000h again to read the buffered byte. This process then continues until you've read the entire "hidden" address space.
How useful this is is debatable, but it will trip up many emulators if you wish to write emulator detection code for some reason (and it would make some dandy on-cart copy protection maybe if you were to map CHR ROM there in certain cases and then read it back discreetly later on).
Posted: Mon Sep 26, 2011 9:56 am
by Dwedit
Nestopia uses a persistent 1-byte buffer for 2007 reads.
You read from the PPU the very first time, you get garbage. (defined as 0xE8)
You read from the PPU again (no matter how much later you are doing this, 5 minutes later or an hour later, doesn't matter), you get the last value read from the PPU, and it puts the newly read byte into the buffer.
Only PPU reads through 2007 affect the 1-byte buffer.
oddly green
Posted: Fri Oct 14, 2011 2:38 am
by Bisqwit
In the same spirit I also ask for clues.
Here is a screenshot from my WIP emulator. Ignore the problem with the messed-up sprite or with the wrong background position. I am at a loss as to what causes part of the text to appear green. It is not even the first time this happens. (I mean, I have ran into the same problem with two previous emulators I have started developing from scratch within 10 years.)
My first intuition would be to check the palette mirroring, but according to my best understanding I have implemented all the palette index mirrorings according to specifications...
That is, all reads/writes to palette indexes, whether internally or through I/O, are routed through the following map:
Code: Select all
00 => 0 08 => 0 10 => 0 18 => 0
01 => 1 09 => 9 11 => 11 19 => 19
02 => 2 0A => A 12 => 12 1A => 1A
03 => 3 0B => B 13 => 13 1B => 1B
04 => 0 0C => 0 14 => 0 1C => 0
05 => 5 0D => D 15 => 15 1D => 1D
06 => 6 0E => E 16 => 16 1E => 1E
07 => 7 0F => F 17 => 17 1F => 1F