Replies: 3 comments 4 replies
-
Maybe we can skip some of the stupidity if the windows docs aren't lying. (Not that I confirmed that any of this is true, just did a basic truth check on the aforementioned claim because it seemed borderline too stupid. There are other Windows APIs this stupid but I always gotta double-check past a certain threshold.) Other relevant info:
|
Beta Was this translation helpful? Give feedback.
-
|
We already set the windows mm timer, of course. This is common knowledge and you can't really make a game without doing so. You should be able to confirm this by reading our codebase. Are you implying otherwise? I'm quite confused by this whole discussion because we're already doing (or have trialled not-yet-implemented because it's not hugely worthwhile, aka spinloops / hybrid approaches) everything you have suggested. I'm also struggling to understand what your graphs are showing, or what is providing them. We cannot reproduce your issues, so I'd suggest it may be a driver or other application bug as you hypothesise. I'd suggest you follow our standard practices as this is likely a localised issue to your configuration. Here's a page with some self-help troubleshooting you can go through https://osu.ppy.sh/help/wiki/Performance_Troubleshooting, and report back if you figure what was causing the issue. |
Beta Was this translation helpful? Give feedback.
-
|
I'm not sure why I'm tagged here, I know nothing about any of this. |
Beta Was this translation helpful? Give feedback.


Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
edit:
My updated views on the issues
please feel free to ask me questions about things that aren't clear or where you need more data.
Im sorry in advance for lacking info or errors.
frame pacing
The primary thing I think can be improved quite easily is how threads idle on windows. The current approach uses a waitable timer, which has an accuracy of 0.5-1ms.
Assuming an average CPU frametime of 1ms the worst case performance loss just from a long sleep call is (frametime + oversleep) doubling frametimes and halving fps to 500. (do keep in mind that there is no guarantee when the sleep call might resume)
Adjusting the time to sleep dynamically based on how accurate the previous sleep was gets compensates how much time performance was lost on average, but doesn't fix the stutters.
500fps is still really good, but encroaches on skipping frame presentations on high refresh rate monitors on systems that should realistically never skip a single one.
Most games don't use or offer any fps limiters above ~300fps for that reason.
waitable timer
A simple spin lock loop calling
yield()on idle practically guarantees the next frame will start when it's supposed to and not 1ms later. With the downside of maxing out that core.yield()spin lock(benchmarks are done in C++ directly via the win32 api or intrinsics where needed)
If the goal is maximum performance this is the perfect solution. If saving resources is the goal this doesn't help.
Sleep calls are optimized by the OS for long sleep times. Luckily CPU makers have special instructions for very precise and energy efficient core parking/sleeping.
Intels
WAIT_PKGinstruction set (Specifically thetpauseinstruction) is purspose built for user space programs that frequently need to sleep for very short amount of times. Unfortunatly .NET support forWAIT_PKGis approved, but still in the backlog for .NET 11 dotnet/runtime#120362 and AMDs equivalent isn't in the discussion at all.Older instructions meant for spins locks are still available in .NET (like x86's
__mm_pause()or ARM'sYIELD). They achieve indentical performance vs ayield()spin lock while reducing power draw.Another benefit is not having to pay the cost of context switches on
yield()or a timer.Most windows games implement a spin loop with
__mm_pause()for the last second and use a waitable timer for every duration above that.Render API
Most of the frametime issues are due to the use of the outdated
veldridlibrary for every non opengl render backend.Veldrid is based of OpenGL 3. An API design very far removed from modern GPUs of every vendor.
The OpenGL backend runs on every platform except apple and performance the best currently.
Newer APIs offer very useful new features (VRR, virtually no input lag in borderless mode, nvidia Reflex/ AMD Anti-Lag 2)
The 4ms jumps in
Frametime-Displayas shown in the videos of my original post due to lacking VRR support.imo veldrid should be replaced with a modern purpose built vulkan backend on every platform.
(non veldrid) OpenGL + Vulkan covers every device Osu!lazer could (or should) run on.
heavy stutters while decoding video
apparently that's a know issue on AMD windows drivers. Unfortunate, because osu maps with a video playing in the background trigger this bug.
original:
This isn't an Idea, but an analysis of lazers bad frame pacing and the pipeline freeze bug. I choose not to put it into an issue, because spaceman_atlas told me to put it here.
Ever since I switched to lazer, my game has randomly massiv stutters, but every time I looked at the ingame fps/frametime counter there was no issue. After testing a bit I found the cause of the stutters.
game running smooth, every frame presented perfectly on time, when testing with no background apps running.
in a normal play session with video running in the background the stutters are back:
each individual frame is rendered on time and with time to spare, but the frames consistently only reach the display in ~16ms.
This hints at three possible causes:
I stumbled across this exact sleep issue when I programmed a very simple bare bones osu!std clone as a learning project. The game couldn't be fps limited to anywhere remotely close to the target frame rate above 500fps.
As it turns out Windows
Sleep()runs at a default resolution of 16.6ms andSleep(0)makes the thread lose it's time slice and has to wait "sleep" until Windows gives it a new time slice.To fix this you have to set the global system timer interrupt frequency to the highest available resolution of glorious
1msor1000hzwithtimeBeginPeriod(1). Only caveat being that this setting effects the timers of every program running on the PC and can be changed by any program on the PC. Best practice is therefore to only use it when necessary and check the current timer resolution before everySleep()call and setting it to the required resolution if necessary withtimeBeginPeriod(1)and reset it once your done requiring it withtimeEndPeriod(1).Even after this complicated setup a
Sleep(1ms)is just a guarantee to sleep atleast1msand the timer resolution might have changed before callingSleep()already.Modern spin lock implementations instead wait on the compositor clock / the swapchain or a waitable timer created with any of the various timer APIs. The first two don't use a timer and the last can have a
Sleep()duration guarantee as low as0.5msIf you need to precisely wait until a known time it is recommended to not sleep only sleep for longer durations and instead busy wait the last
1ms. Power Efficiency can be improved withThread.Yield()and/or the cpus equivalent of intels_mm_pause()instruction.The bug being triggered by OBS being open or running - 30fps
2026-03-11.13-51-29.mp4
60fps
2026-03-11.14-37-33.mp4
All recordings and tests are done with the "Direct3D 11" Renderer, but all (Windows) APIs exhibit similar or identical behavior.
I haven't thought about every possibility and am happy to be reminded about something I have missed.
I also want to talk about the specific implementation in the osu!framework.
Beta Was this translation helpful? Give feedback.
All reactions