85
edits
No edit summary |
No edit summary |
||
Line 13: | Line 13: | ||
It looks like there was an attempt at this already in the code, using the Win32 <code>QueryPerformanceCounter</code> API, called <code>RealSpeedEmulation</code>. Unfortunately it calls <code>QueryPerformanceCounter</code> somehow completely wrong, passing in a member of a record rather than a pointer to that record... No idea why. I just ditched <code>RealSpeedEmulation</code> and did what [https://github.com/Dooskington/GameLad GameLad] does, [https://github.com/SuperDisk/UGE/commit/2caeeb20ac487df4fe4bc2352b9da0ed51eee1b0 which is just spin for a period of time until we need more cycles to be emulated.] | It looks like there was an attempt at this already in the code, using the Win32 <code>QueryPerformanceCounter</code> API, called <code>RealSpeedEmulation</code>. Unfortunately it calls <code>QueryPerformanceCounter</code> somehow completely wrong, passing in a member of a record rather than a pointer to that record... No idea why. I just ditched <code>RealSpeedEmulation</code> and did what [https://github.com/Dooskington/GameLad GameLad] does, [https://github.com/SuperDisk/UGE/commit/2caeeb20ac487df4fe4bc2352b9da0ed51eee1b0 which is just spin for a period of time until we need more cycles to be emulated.] | ||
<pre> | |||
if f_stopped then | |||
while f_stopped do | |||
application.ProcessMessages; | |||
while (cycles < CyclesPerFrame) do | |||
cycles += z80_decode; | |||
cycles -= CyclesPerFrame; | |||
while True do begin | |||
QueryPerformanceCounter(@li); | |||
frameEnd := li.QuadPart; | |||
frameElapsedInSec := (frameEnd - frameStart) / tickFreq; | |||
if frameElapsedInSec > TimePerFrame then | |||
Break; | |||
end; | |||
frameStart := frameEnd; | |||
</pre> | |||
Bam! Already it's almost playable, and the sound isn't causing crashes. I think it was crashing before because the extreme speed of the emulation was causing a sound buffer overrun or something. | Bam! Already it's almost playable, and the sound isn't causing crashes. I think it was crashing before because the extreme speed of the emulation was causing a sound buffer overrun or something. | ||
Line 20: | Line 42: | ||
The emulator actually does render the screen right-side-up when using DirectDraw, but I can't record that with OBS for some reason. It renders flipped graphics when drawing with GDI-- let's fix that. In <code>dib_out.pas</code> I changed <code>biheight := 144;</code> to <code>biheight := -144;</code> and.... | The emulator actually does render the screen right-side-up when using DirectDraw, but I can't record that with OBS for some reason. It renders flipped graphics when drawing with GDI-- let's fix that. In <code>dib_out.pas</code> I changed <code>biheight := 144;</code> to <code>biheight := -144;</code> and.... | ||
Nice. We've eliminated the | Nice. We've eliminated the graphics problem, speed problem, and crashing problem with like 10 lines of code and a 1 character change. All that's really left now is the sound... | ||
= Fixing the sound = | |||
<pre> | |||
var | |||
cs: TRTLCriticalSection; | |||
bufs: array[0..1] of THANDLE; | |||
bufPtr: array[0..1] of pointer; | |||
bufHdr: array[0..1] of THANDLE; | |||
</pre> | |||
The first thing I noticed is that the sound output is going through this convoluted 2-buffer system where it writes to one, and when it fills up, switch to the other, and then provides it to the Windows MME system or something-- I honestly can't really follow it, it's pretty spaghetti. I ripped out all MME stuff and added a call to write to a <code>BASS</code> stream whenever it would normally write to one of these buffers. Sound is still horrible, but at least it doesn't crash when the emulation goes too fast! | |||
It was at this point I noticed something '''really''' curious. | |||
[[File:Curious.PNG]] | |||
<pre> | |||
var | |||
snd: array[1..4] of record | |||
// public: | |||
ChannelOFF: boolean; // (un)mute Channel | |||
// private: | |||
enable: boolean; | |||
Freq: integer; | |||
Vol: byte; | |||
Len: integer; | |||
swpCnt: byte; | |||
EnvCnt: byte; | |||
bit: byte; | |||
cnt: integer; | |||
end; | |||
</pre> | |||
That's right, the volume value for each channel is being stored as an unsigned byte, so it will ''never'' go below zero! I changed <code>vol</code> to be of type <code>ShortInt</code> and.... | |||
Wow, still bad, but a lot better! Another huge gain from an 8 character change! What else can we do? | |||
I noticed this little nugget as well. When the sample is a certain value, it just doesn't write anything. | |||
<pre> | |||
l := shortint(l shr 2) + 128; | |||
r := shortint(r shr 2) + 128; | |||
if (l <> 170) and (r <> 170) then | |||
SoundOutBits(l, r, cycles); | |||
</pre> | |||
I don't know why, so let's try just commenting it out. | |||
WTF? Another huge improvement! We're at a big net negative on code written here, yet it just keeps getting better. What else can we do? | |||
The only problem left here is that channel 3, the Gameboy noise channel, produces a weird waveform instead of what it should. I figured it's probably another sign error somewhere, but it's actually even better: | |||
<pre> | |||
if stage and 1 > 0 then | |||
snd[3].Bit := snd[3].Bit and $f | |||
else | |||
snd[3].Bit := snd[2].Bit shr 4; | |||
</pre> | |||
Do you see it? It's accessing <code>snd[3].Bit</code> in the first branch of the <code>if</code>, but <code>snd[2]</code> in the second one. Let's just change it to 2, and... | |||
Holy shit, it's perfect! And all I did was change like 20 characters! It really strikes me that all Christian Hackbart had to do back in 2000 was do 5 lines of fixes and he would have had a near perfect Gameboy emulator on his hands. Now that I'm done heroically fixing this emulator with my insane hacking skills, I'm off to work on the actual tracker now. | |||
= PS = | |||
I'd just like to point out how amazing it is that the FreePascal folks have recreated the Delphi environment so well that |