11 ms·
Reviving a 19 Year Old Gameboy Emulator
- kevin_thibedeau 8y agoA bit sad that the emulator is running on a personal supercomputer and a spinloop is the solution to slowing it down.
- superdisk 8y agoIf you have any other suggestion I'd like to hear it. Calling sleep or using an OS timer isn't precise enough.
- asveikau 8y agoWhat if you tally up the amount of delay you should have done, then when it gets to a big enough granularity where a sleep call would actually give you that amount, sleep. Eg. Don't try to sleep accurately between the simulated cycles. Let things happen "too fast" for longer and do the delays in batches. Not sure if this would wind up perceivably too bursty. Just an idea based on things I have seen in other domains. For the record I don't think spinning is too bad of it's a very short duration, shorter than the scheduler will give you when you take your thread off the CPU.
- superdisk 8y agoSleep, at least on Windows, is allowed to have +/- 15 ms (default system heartbeat interval) of accuracy, which I don't think would work for this case. https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-sleep https://docs.microsoft.com/en-us/windows/desktop/api/synchap... I could mess around with the Win32 API or the NT API or something to make the heartbeat rate more granular, but according to the docs that kills battery, so I'm guessing it's going to be doing pretty much the same thing as what I'm doing manually.
- asveikau 8y agoYeah I am well aware. I faced issues that reminded me of this when doing some audio stuff. Including on Windows.
- morcheeba 8y ago... that can still work. Measure how much time it actually slept and adjust accordingly. Or, is there some timer that runs at a reasonable rate (100Hz) where you csn yield once you've done 10msec worth of emulation? (I don't know the windows API, so just spitballing from the embedded world here)
- nothanksmydude 8y agoYou seem like you appreciate good timers. If you ever want to revolt in horror/have a few hearty belly laughs, look into how windows has and does handle various granularities of timing
- hrydgard 8y agoRaising the timer interrupt to 1ms with timeBeginPeriod should still be less costly (with regards to power consumption) than a spin loop. But yeah it's all tradeoffs..
- gambiting 8y agoYep, we had this exact issue on our live servers deployed to Windows Server 2016 in a VM - essentially I would do: 1) post tasks to process some data 2) while(tasksNotFinished) Sleep(1); Well, very quickly found out that the Sleep(1) was always a minimum of 16ms(or a multiple of 16ms), which made the whole thing stupidly slow. We fixed it by running timeBeginPeriod(1) which overrides the system timer resolution to 1ms. Yes it would kill the battery on laptops, but on a server this is not an issue.
- asveikau 8y agoThe way I would solve that is an event and a count of tasks. When the last thread to complete its job sees InterlockedDecrement(&ntasks) == 0, call SetEvent to wake up the waiting thread.
- nothanksmydude 8y agoAFAIK, most modern game engines have a threshold of headroom for leftover time that must be hit to eliminate needless spin, but also stay responsive. Back when everyone was targeting 60fps, >~2.25ms of 'leftover time' was needed during the individual 16.6667ms run of that loop to actually trigger sleep instead of spinning. Note that's specific to the graphical rendering thread, and things like the mouse/kb input have their own sleep durations, with or without thresholds.
- deleted 8y ago[deleted]
- pjc50 8y agoIDXGIOutput::WaitForVBlank()?
- nothanksmydude 8y agoDoes this actually work now? It used to be broken to the point that everyone rolled their own
- ungzd 8y agoYou can run period of emulation time at full speed as long as difference in speed is not observable from outside. There's no point to delay regular memory reads and writes by synchronizing every CPU cycle to real time: no one will notice that. If it writes to video memory, result is usually not instantly visible too, and becomes visible only when screen scan position approaches that position in memory. Probably Game Boy has some sprite hardware and changing few registers can cause huge changes on screen, but anyway it's not possible to output changes on screen instantly to host system, so you can draw whole virtual frame at full host speed, considering that you keep track of scan position based on elapsed CPU cycles (T-states or something like that). Once your emulation time period between two screen frames is completed, you can pause emulation until it catches up to next frame in real time, making process sleeping and not consuming CPU in between frames. Input events and sound add additional complexity, but it's doable.
- Doxin 7y agoA nice way to implement this is with a frame budget. You simply keep a variable that keeps track of how many miliseconds of budget you have left. Every time you render to the screen you increase the budget by 1000/fps. Then you simply run the emulation until the budget runs out. if the emulation gets too far ahead of the rendering you can consume extra budget by simply sleeping -- which is now less important to get exactly right, you can simply sleep away 90% of the budget for example, so if the sleep overshoots you've got some play.
- deleted 8y ago[deleted]
- Thorrez 8y agoWere all those errors observable back in 2000? It could be that compiler and operating systems differences now are what caused/revealed some bugs. For example the speed problem was probably much less back then due to slower computers.
- Crinus 8y agoMost likely. For example, this: > passing in a member of a record rather than a pointer to that record... No idea why ...sounds like a difference in how the Windows API was exposed. In Delphi and Free Pascal there are two ways to expose a C function that uses a pointer. This: void Foo(int* ip); can be exposed like this: procedure Foo(ip: PInteger); cdecl; or like this: procedure Foo(var ip: Integer); cdecl; The former allows you to pass any pointer to integer, the latter requires an Integer variable reference (which is implemented by the compiler by passing a pointer to the variable). The difference between the two is that you can pass a Nil (Pascal's NULL) to the first, but the second requires a variable. Older versions of Borland Pascal and Delphi (not sure about newer Delphi versions) used the second declaration style where it made sense, but Free Pascal uses a more direct translation of the Windows headers, opting for the first declaration style. My guess is that the compiler used by the original author of UGE used the first style.
- anyfoo 8y ago> I found (apparently) the only Gameboy emulator written in Pascal, ever: UGE. Hmm, I don't know. If I've learned anything about programming languages, then it's that the progression of programs written in them is usually: #1 Hello World, #2 Factorial, #3 Gameboy Emulator.
- Crinus 8y agoYeah, i think his Google-fu skills need improvement, in another page he mentions that there weren't any IRC clients in Pascal, but i remember at least a couple of IRC libraries for Delphi (and personally i wrote an IRC component in Delphi - you could drop the component on a form and drag-and-drop your way on a simple IRC client, which i used at the time to make dedicated themed clients for the communities i was into :-P).
- SmellyGeekBoy 8y agoI love the content but I also wanted to point out how much I enjoyed your website design. Genuinely confused me for a second!
- saagarjha 8y agoIt's powered by MediaWiki. An interesting choice for a blog, though I guess it gives you thinks like content management and update history for free…
- nothanksmydude 8y agoThis is a fantastic example of my favorite programming technique: When nothing makes sense and you're real emotional about it and want to give up... GIVE UP!* *But you have to write another one from scratch tomorrow because there's good odds you'll do it right on accident and will then be able to diff and understand exactly where you went wrong. Being able to identify this situation and when doing this is useful as early as possible will make you very valuable. The break between attempts is important, go do literally anything else besides code. I once spent a week going in literal circles because of some degree/radian conversion errors. The rewrite took about 15 minutes. Never trust anyone's ability to count, not even your own. It's particularly useful in this exact situation, where the differences are mere +'s and -'s, off by one here or there, maybe a bad copy paste with similar looking variable names. Typically math related
- vanderZwan 8y ago> going in literal circles because of some degree/radian conversion errors Hehe, nice. And thanks for the tip! Retrying tomorrow is nothing new to me, but I never thought to save yesterday's progress and diff it with the next day's rewrite to figure out why I was stuck.
- nothanksmydude 8y agoIt's especially useful when the changes are so small/subtle that you wouldn't even catch them looking at commit history.
- Stunting 8y agoThis connects to Tim Harford's Ted Talk "A powerful way to unleash your natural creativity." Quitting when you hit the wall and moving on to another task will allow your subconscious to work on the problem, often hand delivering it to you when you jump back on your original task. I think knowing I would have to start from scratch the next day would throw that system into hyperdrive. Thanks for sharing! https://www.ted.com/talks/tim_harford_a_powerful_way_to_unleash_your_natural_crea.. https://www.ted.com/talks/tim_harford_a_powerful_way_to_unle....
- userbinator 8y agoThe 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 https://en.wikipedia.org/wiki/Multiple_buffering https://en.wikipedia.org/wiki/Multiple_buffering I've worked with some Windows audio code before and this is the norm --- while it's playing one buffer, you write to the other one, and switch between the two so the sound card gets an uninterrupted stream of data. In earlier versions of Windows (3.x), the DMA on the sound card would read directly from the buffer.
- jplayer01 8y agoI'd say the best part of this whole thing is that the original author even made his code available. If he hadn't, we'd be stuck with another broken implementation that nobody could fix or improve long after the author abandoned the project.