A high-performance, cross-platform game framework written with osu! in mind.
This is the winnerspiros/osu-framework performance fork. It tracks ppy/osu-framework upstream and layers latency/throughput improvements on top. See Changes from upstream for the full diff.
| Category | Highlights |
|---|---|
| Rendering | Multi-backend via Veldrid: Direct3D 11, Direct3D 12, Vulkan, Metal, OpenGL |
| Audio | BASS engine with per-platform low-latency tuning (WASAPI, AAudio, CoreAudio, PipeWire) |
| Input | Raw keyboard, async key events, high-frequency touch, tablet, joystick |
| Low Latency | NVIDIA Reflex / LatencyFlex integration, WASAPI event-driven, AAudio minimal buffers |
| Platforms | Windows, Linux, macOS, Android (API 33+), iOS (13.4+) |
| Runtime | .NET 10, C# 14, System.Threading.Lock, profiled AOT on mobile |
| Testing | Visual test framework, per-component isolation, headless CI support |
| Platform | Renderer | Audio Backend | Low-Latency Audio | Min Version |
|---|---|---|---|---|
| Windows | D3D11 / D3D12 / Vulkan / OpenGL | BASS + WASAPI (shared/exclusive) | โ WASAPI event-driven (~3โ5 ms) | Windows 10+ |
| Linux | Vulkan / OpenGL | BASS + PipeWire / PulseAudio / ALSA | โ PipeWire reduced quantum (~5 ms) | Kernel 5.4+ |
| macOS | Metal / OpenGL (deprecated) | BASS + CoreAudio | โ Reduced I/O buffer (~3โ5 ms) | macOS 12+ |
| Android | Vulkan / OpenGL ES | BASS + AAudio | โ AAudio low-latency mode (~5โ10 ms) | API 33 (Android 13) |
| iOS | Metal | BASS + CoreAudio | โ Reduced I/O buffer (~3โ5 ms) | iOS 13.4+ |
If you want to create a game using the framework:
- Start from the getting started wiki
- Or use the project templates directly
- Full cross-platform support, testing setup, and project structure included out of the box
The rest of this README is for working on the framework itself.
- .NET 10.0 SDK (all platforms)
- Linux: system-wide FFmpeg for video decoding
- Android: JDK 17, Android workload (
dotnet workload install android) - iOS: Xcode 26+, iOS workload (
dotnet workload install ios) - IDE: Visual Studio 2022+, JetBrains Rider, or VS Code with C# + EditorConfig extensions
# Desktop (Windows/Linux/macOS)
dotnet build -c Debug osu-framework.Desktop.slnf
# Android
dotnet workload install android
dotnet build -c Debug osu-framework.Android.slnf
# iOS (macOS only)
dotnet workload install ios
dotnet build -c Debug osu-framework.iOS.slnfIDE users: Load the platform-specific
.slnffile (not the main.sln) for access to template run configurations.
# PowerShell
./InspectCode.ps1
# Bash
./InspectCode.sh
# Code style enforcement
dotnet build -c Debug -warnaserror osu-framework.Desktop.slnf -p:EnforceCodeStyleInBuild=trueThe framework provides a configurable AudioLatencyMode setting that selects the optimal backend and buffer sizes for each platform:
| Mode | Windows | Linux | macOS | Android | iOS |
|---|---|---|---|---|---|
| Standard | DirectSound | PulseAudio defaults | CoreAudio default | AAudio ~512 samples | CoreAudio default |
| Low Latency | WASAPI shared (~3โ5 ms) | PipeWire ~256 samples | CoreAudio ~256 samples | AAudio ~256 samples | CoreAudio ~256 samples |
| Minimal | WASAPI exclusive/min period | PipeWire/JACK ~128 samples | CoreAudio ~128 samples | AAudio ~128 samples | CoreAudio ~128 samples |
Configure via FrameworkSetting.AudioLatencyMode in code or the framework.ini file.
Note: The legacy
AudioUseExperimentalWasapisetting is still supported and interoperates with the new latency mode (enabling low-latency/minimal on Windows will automatically activate WASAPI).
The framework includes a generic low-latency rendering infrastructure:
ILowLatencyProviderโ interface for GPU-side latency reduction (NVIDIA Reflex, LatencyFlex)- Latency markers โ
SimulationStart/End,RenderSubmitStart/End,PresentStart/End,InputSample FrameSleep()โ provider-controlled sleep for Reflex Boost modeLatencyModesetting โOff/On/BoostviaFrameworkConfigManager- Supports both D3D11 and D3D12 native device handles
The agents/ directory contains AI agent instructions for platform-specific performance analysis. Feed these to any AI tool (Copilot, Claude, ChatGPT) to get targeted optimisation suggestions:
| Agent | Focus |
|---|---|
| ๐ Overall | Cross-platform allocations, threading, data structures |
| ๐ฆ Windows | WASAPI, D3D11/D3D12, Reflex, raw input |
| ๐ง Linux | PipeWire/JACK, Vulkan, Wayland |
| ๐ macOS | CoreAudio, Metal, Apple Silicon |
| ๐ค Android | AAudio, Vulkan swapchain, ADPF, 16KB pages |
| ๐ฑ iOS | CoreAudio, Metal TBDR, AOT, thermal management |
Contributions can be made via pull requests to this repository.
If you're unsure of what you can help with, check out the list of open issues.
Before starting, please make sure you are familiar with the development and testing procedure. New component development, and where possible, bug fixing and debugging should always be done under VisualTests.
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured, with any libraries we are using, or with any processes involved with contributing, please bring it up. We welcome all feedback so we can make contributing to this project as pain-free as possible.
This fork has fully removed the osuTK / OpenTK dependency. All math types, the GL binding layer, and the
Keyenum have been replaced with standard .NET / custom equivalents. There are zero remainingosuTKorOpenTKreferences in the framework source.
| osuTK type | Replacement |
|---|---|
osuTK.Vector2 |
System.Numerics.Vector2 |
osuTK.Vector3 |
System.Numerics.Vector3 |
osuTK.Vector4 |
System.Numerics.Vector4 |
osuTK.Matrix3 |
System.Numerics.Matrix3x2 |
osuTK.Matrix4 |
System.Numerics.Matrix4x4 |
osuTK.Quaternion |
System.Numerics.Quaternion |
osuTK.Color4 |
osu.Framework.Graphics.Colour4 (unchanged) |
osuTK.MathHelper |
MathF / float.DegreesToRadians / float.RadiansToDegrees |
All osuTK-era extension methods (Normalized(), PerpendicularLeft(), PerpendicularRight(), NormalizeFast(), PerpDot()) are preserved as extension methods in osu.Framework.Graphics.Vector2Extensions. Matrix operations (TranslateFromLeft/Right, RotateFromLeft/Right, ScaleFromLeft/Right, ShearFromLeft/Right) are in osu.Framework.Extensions.MatrixExtensions.
Convention unchanged: row-vector convention (v * M) is used throughout, matching the System.Numerics and GLSL behaviour.
osuTK's OpenTK.Graphics.OpenGL4.GL.* static methods have been replaced with a custom function-pointer table in osu.Framework.Graphics.OpenGL.GL. On first use the table is populated via Veldrid's OpenGLProcTable (which resolves function addresses from the active GL context using SDL_GL_GetProcAddress). This eliminates the osuTK interop overhead and the dependency on the osuTK NuGet package entirely.
All GL enums used by the renderer (TextureTarget, RenderbufferInternalFormat, BufferUsageHint, etc.) are now defined directly in osu.Framework.Graphics.OpenGL.GL โ no third-party GL bindings needed.
The osu.Framework.Input.Key enum retains its value layout (aligned to SDL scancode order) but is now entirely independent of osuTK. Because Key and InputKey (the key-binding layer enum) have different numeric values, KeyCombination.FromKey() contains a full explicit switch mapping every Key value to its InputKey counterpart โ including all letter/digit/navigation/function/media keys. The fallback is InputKey.None (unknown key) rather than an unsafe cast.
Changes from upstream ppy/osu-framework
This fork (winnerspiros/osu-framework) layers the following on top of upstream. Items are grouped by area; each section lists the what and the why.
Both Veldrid components are consumed as ProjectReferences to git submodules, not NuGet packages. The framework is always compiled against the very latest fork code.
| Submodule | URL | Notes |
|---|---|---|
submodules/veldrid |
winnerspiros/veldrid | net10.0 / C# 14, System.Threading.Lock, full D3D12 backend, hot-path optimisations, vtx/idx buffer caching, glInvalidateFramebuffer |
submodules/veldrid-spirv |
winnerspiros/veldrid-spirv | net10.0, C++17 native side, Android 16 KB page alignment |
Packaging mechanics (so the produced ppy.osu.Framework nupkg is fully self-contained and consumable on nuget.org):
- Both
ProjectReferences usePrivateAssets="all", otherwisedotnet packwould record phantomppy.Veldrid/ppy.Veldrid.SPIRVdependencies pinned to NerdBank.GitVersioning-generated versions (e.g.4.9.111-gโฆ) that don't exist on any feed. - The fork-built managed DLLs (
ppy.Veldrid.dll,ppy.Veldrid.MetalBindings.dll,ppy.Veldrid.OpenGLBindings.dll,ppy.Veldrid.SPIRV.dll) are bundled directly intolib/net10.0/of the framework nupkg via aTargetsForTfmSpecificBuildOutputtarget. - The runtime
PackageReferences the Veldrid fork uses (ppy.Vk,Vortice.D3DCompiler,Vortice.Direct3D11,Vortice.Direct3D12) are re-declared onosu.Framework.csprojso consumers still restore them. - The pre-built C++ native binary
libveldrid-spirvcross.*(fromppy.Veldrid.SPIRVNuGet,IncludeAssets="native") is the only thing pulled from NuGet โ building the C++ side from source would require CMake/clang in CI. That NuGet was itself published fromwinnerspiros/veldrid-spirv@b268bf39ea. submodules/Directory.Build.targetsrewires the SPIRV submodule's staleppy.Veldrid 4.9.69PackageReferenceto a siblingProjectReferenceto the localwinnerspiros/veldridfork. Without this, the old upstreamppy.Veldrid(which lacks theDirect3D12enum value,GetD3D12Info,CreateD3D12) would win on the compile path and break the Windows build.submodules/.editorconfig(root = true) prevents osu-framework's strict style rules from being enforced on third-party fork source files.
The fork's backend-internal optimisations (Vulkan pipeline cache / push descriptors / dynamic rendering / VK_EXT_host_image_copy, Vulkan vtx/idx buffer caching (skips redundant vkCmdBind* GPU calls), Android Vulkan swapchain pre-transform/current-extent handling, D3D12 redundant state caching, D3D12/D3D11/Metal/Vulkan staging-pool swap-remove (O(1) pool reclaim), OpenGL glInvalidateFramebuffer (tile-store skip for offscreen FBOs โ saves tileโDRAM writeback for non-sampled attachments), Vulkan spec compliance (skip clear for transient textures, ยง19.1), modernization sweep (switch expressions, System.HashCode, Array.Empty, string interpolation across all backends), OpenGL pipeline state caching, Metal merged layout-offset loops, all-backend System.Threading.Lock, Vortice.Windows 3.8.3) are transparent โ the framework benefits automatically with no code changes.
The framework explicitly wires the fork's new public API surface (BackendInfoD3D11/D3D12/Metal/OpenGL/Vulkan) in VeldridExtensions.cs:
| Backend | Fork API used | Benefit |
|---|---|---|
D3D11 (LogD3D11) |
BackendInfoD3D11.FeatureLevel, DeviceId |
Avoids materializing a second ID3D11Device COM RCW from the IntPtr just to read one property; surfaces PCI ID for bug reports |
D3D12 (LogD3D12) |
BackendInfoD3D12.SupportsEnhancedBarriers, SupportsMeshShaders, SupportsVariableRateShading, SupportsRaytracing |
Logs full D3D12 capability tier without re-issuing CheckFeatureSupport calls |
D3D11 + D3D12 (ILowLatencyProvider) |
BackendInfoD3D11.Device, BackendInfoD3D12.Device |
Native device handle for NVIDIA Reflex / LatencyFlex on both renderers |
Vulkan (LogVulkan) |
BackendInfoVulkan.AvailableInstanceExtensions, AvailableDeviceExtensions, DriverName, DriverInfo, HasFragmentShadingRate, HasMeshShader |
No more re-issuing native vkEnumerate*ExtensionProperties + unsafe marshalling; surfaces fork-only capability flags |
OpenGL (LogOpenGL) |
BackendInfoOpenGL.Version, ShadingLanguageVersion (cached) |
Read off-thread โ saves two unsafe glGetString + Marshal.PtrToStringUTF8 round-trips inside the GL execution scope; only Renderer / Vendor / MaxTextureSize still require the GL thread |
Metal (LogMetal) |
BackendInfoMetal.MaxFeatureSet, FeatureSet |
Surfaces full supported feature-set count for diagnostics (in addition to the maximum) |
- Added
RendererType.Direct3D12/RendererType.Deferred_Direct3D12andGraphicsSurfaceType.Direct3D12. - Full pipeline:
VeldridDevice.CreateD3D12swapchain creation,LogD3D12diagnostics (adapter info, Enhanced Barriers, Mesh Shaders, VRS, Raytracing),PersistentStagingBufferstaging. - D3D12 included in the Windows renderer fallback order (after D3D11, before OpenGL).
- Powered by the winnerspiros/veldrid submodule's full D3D12 backend.
- Generic
ILowLatencyProviderinterface for GPU-side latency reduction (NVIDIA Reflex, LatencyFlex, or any future API).IDirect3D11LowLatencyProvider(D3D11-specific) extendsILowLatencyProvider.NoOpLowLatencyProviderandNoOpDirect3D11LowLatencyProvider(default no-ops).
- Latency markers (
SimulationStart/End,RenderSubmitStart/End,PresentStart/End,InputSample,TriggerFlash) inserted intoGameHost.UpdateFrame()andGameHost.DrawFrame(). FrameSleep()called at the start of each update frame for provider-controlled sleep (Reflex Boost mode).- Provider auto-initialises on the draw thread using the native D3D11 or D3D12 device handle from Veldrid's
BackendInfoD3D11/BackendInfoD3D12. LatencyModesetting (Off/On/Boost) added toFrameworkConfigManager.- Inspired by upstream PR #6666.
AudioLatencyModeenum (Standard/LowLatency/Minimal) with per-platform backend selection:- Windows: WASAPI shared/exclusive mode, configurable period
- Linux: PipeWire / JACK / PulseAudio with reduced buffer quantum
- macOS: CoreAudio with minimised I/O buffer duration
- Android: AAudio with configurable sample count (-128 to -512)
- iOS: CoreAudio with reduced I/O buffer duration
FrameworkSetting.AudioLatencyModeexposed inFrameworkConfigManager- Interoperates with legacy
AudioUseExperimentalWasapisetting GlobalMixerHandleexposed publicly for external low-latency integrations (e.g. Oboe redirector)
- Unbuffered VSync (
UVSync): limits both draw and update threads to the exact display refresh rate. Useful for VRR / G-Sync / FreeSync displays where regular VSync introduces unwanted buffering (upstream PR #6696). - Custom FPS limiter (
FrameSync.Custom):CustomDrawLimit0โ1000 Hz;0= unlimited draw thread, update thread runs at max Hz. Useful for benchmarking or VRR-specific tuning (upstream PR #6725).
- Raw keyboard input on Windows:
SDL_HINT_WINDOWS_RAW_KEYBOARDenabled by default โ bypasses the Windows message translation layer for lower-latency key events (upstream PR #6507). - Async keyboard event handling: when text input (IME) is not active,
KEY_DOWN/KEY_UPare handled directly in SDL's event filter (HandleEventFromFilter), bypassing the SDL event queue for reduced input-to-render latency (upstream PR #6506).
Every lock (someObject) site in the framework has been migrated from the legacy Monitor-based pattern to System.Threading.Lock, which uses a purpose-built kernel primitive on .NET 9+/CLR and avoids the extra typeof(T) indirection that Monitor.Enter carries. This covers 17 additional lock sites beyond the Veldrid renderer, including:
LockedWeakListโ used by the bindable system on every value propagation and by the renderer's live-texture tracking. TheEnumeratorstruct usesLock.Enter()/Lock.Exit()directly (sinceLock.Scopeis aref structand cannot be held in a regular struct field).TextureStoreโ three separate lock objects (nestedStores,textureCache,retrievalCompletionSources), each upgraded to a dedicatedLockfield.ThreadRunnerโthreadslist lock, taken on everyRunMainLoopiteration.AudioThreadโmanagerslist lock, taken on every audio thread tick.ResourceStoreโstoreslist lock, taken on every resource lookup.SchedulerโqueueLock, taken every frame on each game thread (was alreadyLock; verified consistent).AsyncDisposalQueue,Logger,LoadingComponentsLogger,AggregateBindable,RawCachingGlyphStore,GlobalStatistics,HeadlessGameHost.FastClockโ all lock sites upgraded.
AsyncBufferStream(both the background loader and the consumerRead()path):Thread.Sleep(1)โSpinWait.SpinOnce(). The spinner adapts โ it spins briefly (no OS yield), then yields to the scheduler, then sleeps, rather than always yielding for a minimum of 1 ms.GameThreadSynchronizationContext.Send: same fix. Cross-thread dispatch (used whenever game-thread work is posted from a non-game thread) no longer has a 1 ms floor.
AsyncBufferStream:blockLoadedStatus.All(loaded => loaded)โ previously allocated a lambda + LINQ iterator on every single block load โ replaced withInterlocked.Increment(ref loadedBlockCount) == blockLoadedStatus.Length.ButtonEventManager.handleButtonUp:.Where(d => d.IsRootedAt(โฆ)).ToList()(a newList<Drawable>on every button-release event) โ in-placeRemoveAll.ButtonEventManager.handleButtonDown:InputQueue.ToList()โnew List<Drawable>(InputQueue)(removesSystem.Linqimport entirely from the hot input path).TimedExpiryCache:DateTimeOffset.NowโEnvironment.TickCount64for cache-entry expiry tracking (a singlelongread vs. aDateTimeOffsetstruct construction).GridContainer.distribute:Enumerable.Range(0, n).Where(โฆ).ToArray()+cellSizes.Sum()โ two plainforloops. No LINQ iterator state machines or lambda delegates on layout passes.CompositeDrawableasync load scheduling:loadables.Any(c => c.IsLongRunning)โforloop with earlybreak(no delegate allocation on everyLoadComponentAsynccall).TabControl:items.ToList()foreach during tab removal โ reverse-indexforloop (no copy allocation);SwitchableTabs.Count() < 2โ!SwitchableTabs.Skip(1).Any()(stops after finding two elements).FrameStatisticsDisplay:monitor.ActiveCounters.Any(b => b)โArray.IndexOf(โฆ, true) >= 0(avoids delegate allocation on every statistics poll cycle).JoystickAxisInput: eliminates a double-enumeration ofCount().
AudioAdjustments,AggregateAdjustmentExtensions,SampleChannelBass,TrackBass,GameHostswitch statements โ switch expressions (reduced stack frame usage, branch predictor friendlier).NotifyDictionaryChangedEventArgs:new[] { item }โ collection expression[item](C# 12).
Game threads are given OS-level priorities to minimise scheduling jitter and latency:
AudioThreadโThreadPriority.Highest. BASS's mixer callback is extremely latency-sensitive โ even a few milliseconds of preemption causes audible glitches. The audio thread runs at near-zero CPU utilisation between callbacks, so the elevated priority does not starve other threads.DrawThread/UpdateThreadโThreadPriority.AboveNormal. Ensures the render loop and simulation loop are scheduled promptly and are not delayed by lower-priority background work.- All other
GameThreadsubclasses remain atThreadPriority.Normal(no regression).
DrawVertices is called hundreds of times per frame (sprites, glyphs, effects). Two dictionary lookups per draw were eliminated:
-
Texture binding:
Dictionary<int, VeldridTextureResources>โVeldridTextureResources?[16]flat array. Integer keys are used as direct indices โ zero hash computation. AmaxAttachedTextureUnithigh-water mark means theDrawVerticesloop stops after the highest occupied slot (typically 1โ4) rather than scanning the full array.Array.Clear(16 slots)inBegin()replacesDictionary.Clear(). -
Uniform buffer offsets: the separate
Dictionary<IVeldridUniformBuffer, uint>that tracked per-buffer offsets was eliminated. The offset is now stored inline as the second field of theattachedUniformBuffersvalue tuple โ one fewer identity-hash lookup per UBO per draw call.
DeferredContext.RenderEvents pre-sized to 4096 entries; ResourceAllocator.resources to 512 entries, memoryBuffers to 8. Avoids List<T> capacity doubling during first-frame warmup (default capacity = 4, growing to hundreds of entries over the first frame).
Every Get() / GetStream() call previously acquired a lock and called stores.ToArray() โ allocating a fresh array on each resource lookup (texture loads, font glyph fetches, audio file opens). The stores list is now maintained as a volatile IResourceStore<T>[] snapshot that is atomically swapped only when AddStore / RemoveStore is called (rare, startup-only). Hot reads are lock-free.
The log StreamWriter is now kept open for the lifetime of the logger. Previously the file handle was opened and closed on every 50 ms scheduler flush tick โ adding two syscalls and a heap allocation per tick. The writer is lazily opened on first use, explicitly flushed after each batch, and disposed only on shutdown.
GridContainercell sizing usesRequiredParentSizeToFitinstead ofBoundingBox, avoiding redundant matrix-to-parent-space transforms on every layout pass (upstream Issue #3215).VeldridExtensions.LogD3D11: removed an unusedID3D11DeviceCOM RCW that was being materialised on every device init just to readFeatureLevel.VeldridExtensions.LogOpenGL: hoisted cachedVersion/ShadingLanguageVersionreads out of the GL-thread execution scope (2 fewer unsafeglGetString+Marshal.PtrToStringUTF8calls per init).
- All projects target net10.0 (with
net10.0-androidandnet10.0-iosfor mobile). - C# 14 language features used throughout, including the
fieldkeyword for auto-properties (IDE0032). - CI workflows updated for .NET 10 SDK, Xcode 26.3, and Go 1.26.1.
SupportedOSPlatformVersionbumped from 21.0 โ 33.0 (Android 13 minimum).AndroidManifest.xmlupdated tominSdkVersion="33"/targetSdkVersion="36".- Obsolete
READ_EXTERNAL_STORAGEpermission removed (only applied to API โค 32). - Release config: profiled AOT (
AndroidEnableProfiledAot), partial trimming,AndroidStripILAfterAOT=false(avoidsplt_entrycrashes),EnableLLVMremoved (incompatible with profiled AOT). - Native libraries built with 16 KB page alignment (
-Wl,-z,max-page-size=16384) for Android 15+ compatibility. - SDL3 OpenGL surface setup disables
SDL_GL_FRAMEBUFFER_SRGB_CAPABLEon Android to avoid device-specific colour shifts.
SupportedOSPlatformVersionremains 13.4.- Trim analysis warnings (
IL2026/IL2045/IL2060/IL2070/IL2072/IL2075/IL2091/IL2104) in framework and test code suppressed with[DynamicallyAccessedMembers],[UnconditionalSuppressMessage], and<NoWarn>inosu.Framework.iOS.props.
- All
IDE0032,IDE0055,IDE0057,IDE0042,IDE0062,IDE0270,IDE1006style warnings resolved. - CI
CodeFileSanitystep excludes the veldrid / veldrid-spirv submodule directories. EnforceCodeStyleInBuild=truebuild passes with 0 warnings, 0 errors (includes Roslyn analyser rulesIDE0052etc.).
This framework is licensed under the MIT licence. Please see the licence file for more information. tl;dr you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
The BASS audio library (a dependency of this framework) is a commercial product. While it is free for non-commercial use, please ensure to obtain a valid licence if you plan on distributing any application using it commercially.
| Project | Description |
|---|---|
| osu! | Rhythm is just a click away! |
| GDEdit | A third-party Geometry Dash editor |
| Vignette | OpenCV-based facial recognition for Live2D |
| IWBTM | Platform game with level editor based on "I Wanna..." games |
| DeltaDash | Multi-direction, lane-based scroller rhythm game |
| fluXis | Community-driven rhythm game with creativity focus |
