dirkwhoffmann / vAmiga

vAmiga is a user-friendly Amiga 500, 1000, 2000 emulator for macOS
https://dirkwhoffmann.github.io/vAmiga
Other
293 stars 24 forks source link

Observed behaviors with VSYNC (2.2b1) #753

Closed nicolasbauw closed 1 year ago

nicolasbauw commented 1 year ago

I was very excited to try the new VSYNC functionnality :-)

I've noticed strange behaviors, at 50 Hz:

At 60 Hz, VSYNC works as expected, scrollings are smooth, BUT of course everything speeds up, animations as well as music, so for me 50 Hz is a must.

Test setup : M1 Mac mini, LG HDR 4K.

dirkwhoffmann commented 1 year ago

Hmm, I don't know how to fix the two-monitor scenario. In VSYNC mode, vAmiga is requested to compute a frame from within the Metal drawing routine. This method is called directly by the OS and I have no idea how to influence the number of times this method is called. If I configure my MacBook to refresh at 60Hz, the drawing routine is called 60 times a second. If I configure my MacBook to refresh at 50Hz, the drawing routine is called 50 times a second. So far so good. If I connect a 60Hz external monitor to my MacBook which is configured to refresh at 50Hz, the refresh rate stays at 50Hz. Hence, the internal display seems to dictate the pace. Maybe this can be changed, but I have no idea how.

nicolasbauw commented 1 year ago

I have only have one monitor plugged (via HDMI), and I made my tests with that single monitor, switching from 60 to 50 and vice-versa to do the tests. When in 50 Hz with vsync activated : the CPU clock indicator says 7.10 / 7.11, so it really uses a 50 Hz clock, BUT it does not seem to be vsync'd. I think a try with the MacBook I use for work would be useful, to see how it behaves.

It would be great to have other people's experiences with vsync on their setup.

Gianmarco72 commented 1 year ago

Same here. I've an iMac i5 Retina 4K (2017): with VSYNC ON scrollings are smooth but the CPU clock says 8.50 / 8.53 and everything speeds up (animations as well as music) and the built-in monitor can't switch from 60 to 50 Hz. 😐

Amiga

dirkwhoffmann commented 1 year ago

Same here.

I think we're talking about two different issues. If your built-in monitor refreshes at 60 Hz, vAmiga has to run faster than normal (in PAL mode), because it is forced to refresh with the same frequency as your monitor (this is what VSYNC is all about). Unlike in modern computers, the frame rate of the Amiga is always proportional to the speed of the CPU.

In @nicolasbauw's case, the external monitors refreshes with a different rate. Now, the Mac has two options for the graphics loop: To invoke it with the refresh rate of the internal monitor which has to results in jitter on the external monitor, or to invoke it with the refresh rate of the external monitor which has to results in jitter on the internal monitor. It seems like the Mac always uses the frequency of the internal monitor for the graphics loop. It might be adjustable by some macOS API function, but I don't know how.

nicolasbauw commented 1 year ago

Does that mean that even for the Mac mini (which has obviously no monitor), the monitor is considered as an external monitor (from code point of view) ?

dirkwhoffmann commented 1 year ago

I've found a well-done video about adaptive sync: https://developer.apple.com/videos/play/wwdc2021/10147/

Adaptive sync isn't what we need or want, but I've learned that MTKView::preferredFramesPerSecond is writable. E.g., if in vAmiga's Metal setup code this value is set to 30, vAmiga runs at half pace:

view.preferredFramesPerSecond = 30
Bildschirm­foto 2022-11-20 um 11 01 13

Thus, a solution could be to figure out the refresh rates of all connected displays and to set preferredFramesPerSecond to the one of the external monitor.

dirkwhoffmann commented 1 year ago

Update: It's possible to get notified when a window is dragged to another screen. This is done by implementing the following function in the window delegate:

    public func windowDidChangeScreen(_ notification: Notification) {

        debug(.vsync)
        if #available(macOS 12.0, *) {
            adjustRefreshRate(rate: window!.screen!.maximumFramesPerSecond)
        }
    } 

The shown implementation calls adjustRefreshRate with the refresh rate of the new display.

My plan was to modify preferredFramesPerSecond inside adjustRefreshRate to adjust the frequency of the drawing loop. However, this doesn't work as expected. It seems like preferredFramesPerSecond is only read once when the Renderer is initialized. Modifying the variable afterwards seems to have no effect.

dirkwhoffmann commented 1 year ago

This article provides some more background information about the drawing routine I am currently using:

When is draw(in:) called?

The MTKView class supports three drawing modes and you need to change the properties before drawing.

  • The default automatically invokes a redraw based on an internal timer. In this case, both isPaused and enableSetNeedsDisplay are automatically set to false.
  • Draw by calling setNeedsDisplay(): In this case, both isPaused and enableSetNeedsDisplay must be set to true.
  • Call draw() explicitly: This method manually asks the view to draw new contents. In this case, isPaused must be set to true and enableSetNeedsDisplay must be set to false.

I think what had to be do done is option 3. I would have to manage my own CVDisplayLink timers and utilize them for calling draw(in:) at the right pace.

I am not sure if I want to go for it, because it's supposedly a lot of work until everything works as expected (GPU stuff is complicated and I am not an expert in this area). In addition, the problem will vanish by itself in future when older Macs get replaced by newer ones equipped with ProMotion technology (l think all ARM based Macs support this feature already). Therefore, I think other issues have higher priority. However, if an easier solution comes up, I am eager to implement it.

BTW, this article is interesting, too.

nicolasbauw commented 1 year ago

The available refresh rates in the preferences seems to be machine dependant : the Macbook I use for work (a 2019 Intel 15") only proposes 30 and 60 Hz with the exact same monitor (I can select 50 Hz with my Mac mini), so I can't try with this machine.

nicolasbauw commented 1 year ago

Interesting : even if vsync has issues (scrollings still choppy), I've found that at 50 Hz and vsync active, I do not longer have the sometimes sticky mouse pointer in the Dune game (that happened when moving the mouse slowly in previous versions).

dirkwhoffmann commented 1 year ago

I do not longer have the sometimes sticky mouse pointer in the Dune game

This was (most likely) due to another bug which I fixed on the way. When a frame got skipped due to whatever reason, the GUI code got offered two long frames in a row (or two short frames in a row). The old code thought that it had already received this emulator texture and waived to update the GPU texture (to save time). The new code does the comparison based on frame numbers.

dirkwhoffmann commented 1 year ago

I close this issue for now, because I don't know how to improve the code. If new ideas come up, please reopen.