Edit:
Visitors finding this thread, you can download builds implementing Lag Reduction from the Nightly Builds page. (Link goes to Windows 64-bit, but you can find other builds around there too).
Original post:
For a game, there are several critical timings: The time of the joystick input, the time the game has decided what to display on the screen, and the time the image actually appears on the screen.
For the Atari 2600, there is zero input lag. Input is sampled and the game logic runs during vblank time, then the screen image is generated as the screen renders using the new information about the game’s state.
But for the NES and later 2D consoles, the game logic runs during render time. So as the game is reacting to your input, it is displaying the previous frame on your TV, and you wan’t see the effects of your joystick input until the next frame. This is one frame of internal lag.
Sonic the Hedgehog games on the Genesis happen to have two frames of internal lag.
Anyway, the “gens-rerecording” emulator has an example of how to deal with internal lag, as seen in the basic update_frame code:
int Update_Frame_Adjusted()
{
if(disableVideoLatencyCompensationCount)
disableVideoLatencyCompensationCount--;
if(!IsVideoLatencyCompensationOn())
{
// normal update
return Update_Frame();
}
else
{
// update, and render the result that's some number of frames in the (emulated) future
// typically the video takes 2 frames to catch up with where the game really is,
// so setting VideoLatencyCompensation to 2 can make the input more responsive
//
// in a way this should actually make the emulation more accurate, because
// the delay from your computer hardware stacks with the delay from the emulated hardware,
// so eliminating some of that delay should make it feel closer to the real system
disableSound2 = true;
int retval = Update_Frame_Fast();
Update_RAM_Search();
disableRamSearchUpdate = true;
Save_State_To_Buffer(State_Buffer);
for(int i = 0; i < VideoLatencyCompensation-1; i++)
Update_Frame_Fast();
disableSound2 = false;
Update_Frame();
disableRamSearchUpdate = false;
Load_State_From_Buffer(State_Buffer);
return retval;
}
}
So what it does is this: (to compensate for N frames of latency)
- Run one frame quickly without outputting video or sound
- Save State
- Run N - 1 frames quickly without outputting video or sound
- Run one frame with outputting video and sound
- Load State
edit: fixed the typo on third bullet point, thanks Brunnis