Hi, sorry, another emulator author here, jumping in after a brief email discussions.
My emulator supports a handful of early-'80s machines, but does so with two relevant features:
- all produce a real video signal, to talk to an emulated CRT. Video output looks like sync level for n units of time; then this PCM data at this clock rate for m units of time; then this constant level for of units of time; etc. It's not "line based" or any other reductive term like that, it's a real video signal. Many of the machines batch up their PCM periods but it's never as much as a whole line. For composite colour machines, the signal a real NTSC signal that your GPU really decodes, etc;
- it's predicated around on-demand emulation. The input is always that it's now time X, so would the emulator please catch up to then. Prompting events are currently either audio exhaustion (every 5–10ms, depending on your output rate — it'd scale up to megahertz if your audio card did, but permits itself some latency so as to cooperate nicely with power management) or vertical sync.
On the receiving end of the video signal is the emulated CRT. So it maintains an actual pretend flying spot, discerns horizontal and vertical syncs and attempts to phase-lock to them, and paints a pretend CRT surface.
Heresy here: I'm a lot more annoyed by lag and by motion aliasing than I am by blurring. So I prefer blurring to motion aliasing. Also I'm originally British and a Mac user who is not in any other way a gamer, which means that most of the time I want to play my 50Hz games on a fixed-60Hz display. So I have an approximation of phosphor decay in there. Which, if I were playing devil's advocate, I'd describe as the specific addition of blurring to avoid 50->60 stuttering. It also decreases input lag though, so I call it a double win. But it's the opposite of Blur Busting. Damn me!
Also because I'm painting the screen with a bunch of individual bits of geometry to represent each raster sweep it means I get deinterlacing that isn't terrible for free. The alternate frames are at different vertical offsets because the machine signalled sync at a different time, and they're blended because I have pretend phosphors.
So, cool, that's me. Pulling around to relevancy:
1. I think the problem of syncing my pretend CRT with discerned incoming syncs is exactly the same problem as syncing to real machine vertical syncs based on a noisy trigger. In both cases we're talking about a phase-locked loop with a low-pass filter. So I might be interested in discussing that more closely. My solution mimics that which I found in the CRT literature, being a flywheel sync. So it's always spinning and it triggers the actual (pretend) flyback upon the completion of each metaphorical revolution. When it receives a sync trigger from the feeding signal that prompts a comparison with what the flywheel already believes. The flywheel can't change phase but it can change frequency. So it shifts itself in the direction of the error proportionally to the error subject to a cap.
That's my specific IIR implementation of a low-pass filtering phase-locked loop. You could just as easily go FIR; I'm using that for the pretend PLL that pretends to interpret flux signals when emulating a disk drive and have found it to be robust. There's quite a lot of literature in the Atari ST world on exactly how the WD177x implements a digital FIR PLL, none of which I have read. But it's on my mental list.
2. For any public library, I would advocate for the same sort of event-based API. What would be ideal to me would be no more than giving you two callbacks and telling you to go, and you calling it periodically, those being:
Code: Select all
outputUpTo(distance down display) -> amount of emulated time I expended to get there
setSpeedMultiplier(multiplier)
A low-level variant would allow me to announce whatever my OS is telling me about vertical syncs and, if available, specific line times so that I could bind it to my OS of choice but for anything mainstream I'd dare imagine it's just a case of asking for you to go and responding to the callback. You tell me where you think I should down to, I tell you how much emulated time I took to get there.
That gives you enough information to pick any slicing strategy, decide whether I'm close enough to in-sync with the display to justify beam racing at all, and to warn me if you're changing the meaning of time so that I can adjust my expectations around the other time-based outputs, like audio.
Re: my emulator, I've a few platforms in there with programmable field timing so if and when I get around to trying to adapt this fantastic new idea in lag elimination, I'm actually going to need to up my game so that I'm sync monitoring on both sides and making appropriate instantaneous decisions about emulated speed to line the two up if and when frequencies are appropriately close.
Also mine is a proper native app on the Mac at least, which makes a common-enough use case being to have multiple emulated machines all over your desktop, and something like 90% of Macs are laptops nowadays so I'm not sure that a busy loop will be acceptable to most of my users. Which makes for a bunch of extra factors.
Sadly it's not priority one so I'm unlikely to act all that soon, but the new idea is really exciting and when I've done it once, it'll naturally flow into all of my current machines and any additional ones I implement from now on. So it will get done.
EDIT: and re: whether to handle 120Hz display of 60Hz as a double-speed burst for the first frame followed by a repeat or a blank, or to abandon raster racing, I think I'd prefer the latter because latency is my overriding concern. Yes, more than blur — sorry! I'm emulating early-'80s machines so the real experience would have been blurry but lag free, so that's just trying to be accurate. Therefore I'd probably be likely to fall back on longer phosphors and not racing the beam, just as you'd get if you used my emulator today.
EDIT2: oh, and audio latency too if you tried to fit 60Hz to 120Hz by taking every other frame off. I think that, in summary, my perspective is that there are at least three latency factors at work here: input, video and audio, and I don't agree that any one trumps the other two.