Jump to content
IGNORED

Analog input lag in Stella?


archagon

Recommended Posts

My coworkers and I had a ton of fun playing Medieval Mayhem on a real Atari 2600. Unfortunately, we no longer have access to the original hardware, so we decided to buy a pair of 2600-daptors and play the game on Stella instead. The game works, but there's a noticeable delay between using the spinner and the paddle moving — not so high that the game is unplayable, but clearly not as smooth as the real thing. If you bump up Stella's framerate manually, the latency disappears but the game runs at breakneck speed. This happens with the mouse as well, so it's not just a 2600-daptor/USB/driver problem.

 

It almost feels like when the game is running at 60fps (the normal framerate), the input and display refresh are actually capped at 30fps, even though the animations and music play at the correct speed.

 

Is there any way to fix this issue? Thank you!

Edited by archagon
  • Like 1
Link to comment
Share on other sites

It almost feels like when the game is running at 60fps (the normal framerate), the input and display refresh are actually capped at 30fps, even though the animations and music play at the correct speed.

 

Is there any way to fix this issue? Thank you!

Unfortunately this is a problem that is inevitable on frame-based emulators. On a real VCS the controllers are read directly while the console draws the current frame. But on an emulator you first have to read the PC controller, then feed it's data into the emulator to render the next frame, and only then can you display the output. So depending on how you read the PC controllers, there always is a one or two frame delay between when you move the controller and when you see the objects move on the screen.

  • Like 1
Link to comment
Share on other sites

Yep, Eckhard has explained exactly what's going on. Stella (and every other emulator that I'm aware of) has a main loop something like this (warning, developer mode follows):

 

DO
   read controllers from host system
   render video frame
   waste time until 1/60 of a second has passed
LOOP

 

The problem is, in the process of rendering a frame, an instruction occurs that reads from the 'virtual' controller port to get the current paddle position However, at that point in time, the actual controller has already been read from the host OS, probably some milliseconds beforehand. So the movement that occurs 'right now', during the frame rendering, won't be processed until the next frame. When you speed up the framerate, you decrease the amount of time you have to wait for the next frame to happen, so it seems like the lag disappears. But it only gets smaller; the same thing is happening behind the scenes.

 

I guess to get around this, you'd need the emulated 6532 to be a pull device, where it polls the PC controller when it is accessed with a read command. Right now, it's a push device, where the PC controller is polled beforehand, and the data is pushed to it after a 6532 read actually happens. But there's another issue as well; you can't be sure that the PC controller doesn't have buffering at the OS level. By that, I mean there's probably a delay between when the controller button/axis is pressed, and when the OS tells an application about it. This is yet another source of 'lag', and is completely outside the emulators control.

 

Anyway, sorry for the long-winded, technical explanation. I've often been told that most people don't care about that stuff, but it interests me, so there it is.

 

Long story short, there's nothing that can be done about it right now :(

  • Like 2
Link to comment
Share on other sites

Thank you for the explanation! I'm glad I'm not going crazy. Do you think the problem would be fixed if the controller was read on a separate thread? If so, I could try poking around the source code.

No, that wouldn't improve the situation much. The timing in the emulator is frame based. This means that you need to render the frame entirely before you can display it. Therefore there will alway be a one frame delay between when the code for reading the controller is emulated and when the resulting output gets displayed.

  • Like 1
Link to comment
Share on other sites

Oh, I see what you're saying. But if the game is running at 60fps, this means that Stella should be able to pull off 1/60 second controller delay, right? Instead, it feels more like 100ms. And again, with the framerate boosted to 120fps, the lag disappears, so it's not a driver issue.

 

Also — on the Atari, the drawing is scanline by scanline, right? So won't the paddle area still get redrawn only once every 1/60 seconds?

 

Thank you for the help!

  • Like 1
Link to comment
Share on other sites

Paddle games complicate things as well as the Atari doesn't have any special hardware to process paddles. On later systems, you just read a register on one of the IO chips and it returns a value from 0-255. On the Atari you must read the paddle about every other scanline during the Kernel (the part of the program that draws the screen) and programmatically figure out how far the player has rotated the paddle. It's rather time consuming to do so, using about 18% of the CPU time on the scanlines that you read the paddle, which is why paddle based games tend to be rather simple looking.

 

In Warlords only 1 paddle is read each frame, so each paddle is only read every 4th frame. That's quite a bit of lag, even on the real hardware. Carla Meninsky had a couple routines, that I called SmoothXmotion and SmoothYmotion, to help to hide the lag. When I disabled those routines Warlords was very jerky.

 

When I wrote Medieval Mayhem, I was able to utilize the extra ROM space (Warlords is a 4K game, Medieval Mayhem is 32K) to make it so Medieval Mayhem reads 2 paddles every frame, so each paddle is read every other frame. This is quite a bit faster than Warlords, so I did not use any functions like SmoothXmotion and SmoothYmotion.

 

In both games all paddles get read, even if it's just a 1 player game, so the lag is always present.

  • Like 1
Link to comment
Share on other sites

Warning, techincal wanna-be-developer mode :-)

 

The below is based on looking at a version a little earlier than the current release, but I suspect this has not changed.

 

In M6532.cxx in the peek method there is

 

case 0x00: // SWCHA - Port A I/O Register (Joystick)

{

uInt8 value = (myConsole.controller(Controller::Left).read() << 4) |

myConsole.controller(Controller::Right).read();

 

Digging in the controller class, the read method just appears to return values out of an array (myDigitalPinState[] or myAnalogPinValue[]) that were set at an earlier time.

 

Would calling the controllers update method, which I believe is where the virtual controllers (the values returned by the read method) are updated from the real inputs (joystick, keyboard, mouse, etc), take care of this? -

 

case 0x00: // SWCHA - Port A I/O Register (Joystick)

{

myConsole.controller(Controller::Left).update();

myConsole.controller(Controller::Right).update();

uInt8 value = (myConsole.controller(Controller::Left).read() << 4) |

myConsole.controller(Controller::Right).read();

 

 

Note only the first 4 pins of the DB9 go to the M6532. The paddle inputs, pins 5 & 9, actually go into the TIA. In TIA.cxx in the peek method, there is a simular situation -

 

switch(addr & 0x000f)

{

...

case INPT0:

value = (value & 0x7F) |

dumpedInputPort(myConsole.controller(Controller::Left).read(Controller::Nine));

break;

 

case INPT1:

value = (value & 0x7F) |

dumpedInputPort(myConsole.controller(Controller::Left).read(Controller::Five));

break;

 

case INPT2:

value = (value & 0x7F) |

dumpedInputPort(myConsole.controller(Controller::Right).read(Controller::Nine));

break;

 

case INPT3:

value = (value & 0x7F) |

dumpedInputPort(myConsole.controller(Controller::Right).read(Controller::Five));

break;

 

So, I again wonder if it is a matter of adding calls to the controller update method before each invocation of the read method.

 

/wanna-be-developer

 

Tom

  • Like 1
Link to comment
Share on other sites

First of all, that section of code has changed in the latest releases, so what you have above is a little out of date.

 

Anyway, I don't want to get too deep into this, since it can quickly get very complicated. This is the order of processing:

  1. Controller is read from the host in EventHandler::poll()
  2. Results from actual controllers are cached in virtual Controller objects (ie, in myDigitalPinState[] andmyAnalogPinValue[])
  3. Various M6532::update() method is called at the end of EventHandler::poll(), which processes the arrays above

In the current code, the controller update() methods are already moved inside M6532. I'm not sure it will fix the problem you're seeing, though, since a 'read' from a virtual controller is just returning cached data in the various arrays. The main problem is that at that point in time, there may be new controller data potentially coming in from the host system, but it will be done in EventHandler::poll(), and that's not where we are right now. So the issue is one of timing, and the ordering of doing the operations.

 

Perhaps a timeline would help explain. Consider the current algorithm in more detail:

1. Read actual controllers from host in EventHandler::poll()

1. Poll system

2. Call 6532::update(), which processes/caches the events from reading the controllers into the various virtual controller arrays

 

2. Update framebuffer (a full frame, consisting of multiple scanlines), by running the CPU an appropriate amount of time

1. At some point during the run, the ROM will read from 6532 to get controller state; it gets the cached info instead

 

So the problem is that by the time we're at 2.1 and wanting controller info, you're going to get the cached version. But you might be moving the paddle in real-time at that point, and that info won't be available until the next frame.

 

This is the push method, since the controllers are polled up-front, the results cached, and pushed to the objects that need them. The other option is the pull method, where the controller polling (part 1.1) is put directly inside part 2.1. IOW, the 6532 pulls/requests the info when necessary. But this creates other issues I won't get into right now.

 

Finally, I'm not entirely convinced any of this will make a difference at all. AFAICT, there are at least 3 things happening, and all could contribute to lag:

  1. The Stelladaptor/2600-daptor itself might have internal buffering, as it's polling and sending data over USB. This cannot be fixed in Stella at all.
  2. Once the OS reads the controller, it might have buffering too, in that the event is placed in a queue or something, and not available right away (again, nothing Stella can do about it).
  3. Once Stella actually gets the event, it has it cached/buffered too. This may be somewhat fixable.
  4. If you're using the mouse to emulate the paddle, the emulation itself might not be exact. Again, this may be fixable.

I'm not saying it's a lost cause and there's no room for improvement, but there are some major obstacles to overcome. In the first 2 cases, it can't be fixed. For the third case, it involves a complete reworking of event handing. The 4th is probably the easiest, but doesn't address your specific issue.

 

This is all a consequence of emulating an extremely simple console on a multi-tasking operating system with many levels of software. The 2600 console was extremely simple in that pressing a joystick direction literally grounded the input, and the 6532 was connected directly to that input line. A modern OS is much more complicated.

Link to comment
Share on other sites

I should also add that #4 in the previous post doesn't apply just to the mouse; there's a similar section of code that's associated with the Stelladaptor/2600-daptor too. In all honestly, that's where I'd start with trying to fix this, since (a) it's already been requested, and (b) #3 will probably never happen.

 

To get more accurate paddle emulation, what I'd need is some way of comparing to a real system, some test ROMs, something ... Simply playing a game and saying there's lag doesn't help with figuring out where the lag is, or why it's happening. Maybe someone can come up with test ROMs that measure the timing between moving the spinner a pre-defined distance, and somehow present some info onscreen that would be useful. Basically, something I can run in Stella and on a real system and clearly see the difference, and when/how it occurred.

Link to comment
Share on other sites

How about Stella anticipating (guessing) the next frame from the previous one(s)? You know that there is a lag of at least one frame, so could do add a correction term:

 

Something like that:x'[now] = x[now] + (x[now] - x[now-1])/c; (c>=1, depending on how much correction you want)

 

For larger lags one could go back more frames.

 

Of course there is a problem with abrupt rotating speed/direction changes. But maybe it still "feels" better.

Edited by Thomas Jentzsch
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...