From 3d3f5122a350e4dd4752c9b1d7cd97b372d20d43 Mon Sep 17 00:00:00 2001 From: Shibco Date: Sun, 2 Aug 2026 15:05:12 +0200 Subject: [PATCH 1/3] Multi audio device hardening: pipeasio 0006 and 0007 When Live's input and output are separate devices, each has its own sample clock and PipeWire adjusts one device to follow the other. 0006: the driver caches each device's card, keeps a user's device choice untouched, anchors the fallback capture pick to the playback card so the driver's own tie-breaking never adds a second clock, and logs the device pair once when a session spans two devices. 0007: while a session plays across two devices, the driver gives the following device 512 frames of buffer room (api.alsa.headroom, applied to the live node) so the clock adjustment stays silent, and puts the old value back afterwards. PIPEASIO_FOLLOWER_HEADROOM changes or disables it. Sessions on one device are never touched. Build-audit fingerprints and the frozen series cover both patches. Carries the performance-moonshot research notes, synthesis, and tracker into the branch. Open: the two-device listening run on real hardware, and reporting the added buffer room to Live as latency. --- notes/ABLETON-WINE-PIPEASIO-CRACKLE.md | 32 +- .../MOONSHOT-ROADMAP-TRACKER.md | 22 +- .../SYNTHESIS-2026-08-01.md | 514 ++++++++++++ .../docs/ABLETON-LIVE-TECHNICAL.md | 222 ++++++ .../docs/ABLETON-WINE-PERFORMANCE-PLAN.md | 509 ++++++++++++ .../docs/AUDIO-LATENCY-ECOSYSTEM.md | 134 ++++ .../docs/MOONSHOT-OPPORTUNITIES.md | 179 +++++ .../docs/OTHER-FORKS-SURVEY.md | 127 +++ .../docs/OWN-FORK-BUILD-AND-RUNTIME.md | 136 ++++ .../docs/OWN-FORK-PATCH-MAP.md | 195 +++++ .../docs/PERFORMANCE-MOONSHOT-2026-08-01.md | 754 ++++++++++++++++++ .../docs/PERFORMANCE-MOONSHOT-RESEARCH.md | 299 +++++++ .../docs/PROJECT-TECHNOLOGIES.md | 399 +++++++++ .../docs/PROTON-GRAPHICS-AND-RUNTIME.md | 339 ++++++++ .../docs/PROTON-SYNC-AND-CPU.md | 289 +++++++ patches/SERIES.sha256 | 2 + ...allback-capture-report-clock-domains.patch | 207 +++++ ...give-the-follower-device-buffer-room.patch | 377 +++++++++ scripts/build-audit.sh | 2 + 19 files changed, 4734 insertions(+), 4 deletions(-) create mode 100644 notes/performance-moonshot/SYNTHESIS-2026-08-01.md create mode 100644 notes/performance-moonshot/docs/ABLETON-LIVE-TECHNICAL.md create mode 100644 notes/performance-moonshot/docs/ABLETON-WINE-PERFORMANCE-PLAN.md create mode 100644 notes/performance-moonshot/docs/AUDIO-LATENCY-ECOSYSTEM.md create mode 100644 notes/performance-moonshot/docs/MOONSHOT-OPPORTUNITIES.md create mode 100644 notes/performance-moonshot/docs/OTHER-FORKS-SURVEY.md create mode 100644 notes/performance-moonshot/docs/OWN-FORK-BUILD-AND-RUNTIME.md create mode 100644 notes/performance-moonshot/docs/OWN-FORK-PATCH-MAP.md create mode 100644 notes/performance-moonshot/docs/PERFORMANCE-MOONSHOT-2026-08-01.md create mode 100644 notes/performance-moonshot/docs/PERFORMANCE-MOONSHOT-RESEARCH.md create mode 100644 notes/performance-moonshot/docs/PROJECT-TECHNOLOGIES.md create mode 100644 notes/performance-moonshot/docs/PROTON-GRAPHICS-AND-RUNTIME.md create mode 100644 notes/performance-moonshot/docs/PROTON-SYNC-AND-CPU.md create mode 100644 patches/pipeasio/0006-anchor-fallback-capture-report-clock-domains.patch create mode 100644 patches/pipeasio/0007-give-the-follower-device-buffer-room.patch diff --git a/notes/ABLETON-WINE-PIPEASIO-CRACKLE.md b/notes/ABLETON-WINE-PIPEASIO-CRACKLE.md index 170f8a4e..c4af52e4 100644 --- a/notes/ABLETON-WINE-PIPEASIO-CRACKLE.md +++ b/notes/ABLETON-WINE-PIPEASIO-CRACKLE.md @@ -375,4 +375,34 @@ it was written 2026-07-26. State of the plan as of this addendum: the session log with their own `[pipeasio]` prefix, and its wine TRACE class did not surface even with `trace+asio`, so the G1 log line is invisible in the field; the behavioural answer stands. -- Next per the Order section: F8. G2 through G4 stay open. +- F8 landed 2026-08-02 as pipeasio patch 0006, in the conservative reading + the risk table asks for. The node cache carries `device.id`, so the + driver can tell when two nodes share a card. The preference change is + confined to the fallback tier: only when the capture direction has no + usable default does the driver prefer a source on the playback card over + the first-discovered one, logging the choice when it diverges. A default + the user set is never overridden, because recording from a device the + user did not pick is a worse failure than resampling; when the two + directions resolve to different devices, the driver logs the pair once, + naming both nodes and the `input_device`/`output_device` keys (marker + `pipeasio-clock-domains`). Runtime verification needs a container + build. The seeded input-count question (whether the seed keeps + `inputs = 2`) stays open, decided separately per the risk table. +- C9 correction and fix, 2026-08-02: two devices only crackle when the + second device runs out of buffer room while PipeWire adjusts its pace + to match the first device's clock; with enough room the adjustment is + silent (C8). Pipeasio patch 0007 supplies the room. While Live runs on + two devices, the driver raises the second device's buffer room to 512 + frames (the ALSA property `api.alsa.headroom`, applied live through + the node's Props parameter), logs the change, and puts the old value + back when the session ends, the devices change, or that device starts + setting the graph's timing itself. Single-device sessions are never + touched. `PIPEASIO_FOLLOWER_HEADROOM` sets a different frame count or + turns this off. The live property path is verified against PipeWire + 1.6.8 on the build machine. Open: a listening run on a real rig (USB + microphone plus a separate USB interface, no crackle over a long + session), the oldest supported PipeWire (the driver does nothing when + the property is refused), and reporting the added room to Live as + latency, which recording alignment needs. +- Next per the Order section: the reply to #49, then the + three-distribution verification matrix. G2 through G4 stay open. diff --git a/notes/performance-moonshot/MOONSHOT-ROADMAP-TRACKER.md b/notes/performance-moonshot/MOONSHOT-ROADMAP-TRACKER.md index dbeb18e5..cb8aa0b9 100644 --- a/notes/performance-moonshot/MOONSHOT-ROADMAP-TRACKER.md +++ b/notes/performance-moonshot/MOONSHOT-ROADMAP-TRACKER.md @@ -13,7 +13,7 @@ entry. | # | Item | Certainty | Invasiveness | Projected result | Chance | Status | |---|---|---|---|---|---|---| | P0 | Bench baselines + harness automation | certain | none | evidence floor for everything | high | in progress | -| P1 | Host `/dev/ntsync` launch gate | high | minimal | recovers a silently lost ~1 core / 4-50x sync on affected hosts | very high | in progress | +| P1 | Host `/dev/ntsync` launch gate | high | minimal | recovers a silently lost ~1 core / 4-50x sync on affected hosts | very high | done | | P2 | Run the written scheduling A/B | n/a (measurement) | none | decides the RR default, quantifies the inversion | high | in progress | | P3 | Audio hardening F0-F8 + PipeWire host arm | high | medium | fixes the audible defect classes (issue 49, crackle, recording offsets) | high | in progress | | P4 | Thread-priority chain (avrt de-stub, server RT band, retire whole-process RR, RTKit) | high problem / medium gain | high | audio outranks UI per thread; biggest dropout-margin lever at 64-128 frames | medium-high | not started | @@ -67,8 +67,24 @@ entry. with PIPEASIO_ALLOW_QUANTUM_MISMATCH=on as the escape hatch, and port buffers sized to the quantum limit; verified in production the same day (pre-launch pin 384 predicted cleanly, mid-run pin 768 converged with - one mute episode; G1 answered yes). Next: F8. Driver patches need a - container build to ship. + one mute episode; G1 answered yes). F8 landed 2026-08-02 as pipeasio + patches 0006 and 0007. 0006: device.id cache, fallback capture + anchored to the playback card, user choices never overridden, neutral + two-device log line. 0007: while Live runs on two devices, the driver + gives the device that is not setting the graph's timing 512 frames of + buffer room (`api.alsa.headroom`, applied live) so PipeWire's pace + matching stays silent, and restores the old value afterwards; + `PIPEASIO_FOLLOWER_HEADROOM` adjusts or disables it; single-device + sessions are untouched. Verified: the live property path on PipeWire + 1.6.8. Open: the two-device listening run (USB microphone plus a + separate USB interface, no crackle over a long session), reporting + the added room to Live as latency, the container build that carries + the driver stack, and the seeded input-count question. Next: the + issue 49 reply, the latency reporting, then the three-distribution + verification matrix before any release carries these patches. +- P1 shipped 2026-08-02 (commit 96b043f, in the production launcher): the + warning branches are verified against faked kernels; a run on a host + that really lacks `/dev/ntsync` is the one open check. - Wine patches from moonshot items land in `patches/performance/`; apply-order wiring in `container-build.sh` and build-audit fingerprint entries land with the first such patch. P1 produced no patch (launcher, diff --git a/notes/performance-moonshot/SYNTHESIS-2026-08-01.md b/notes/performance-moonshot/SYNTHESIS-2026-08-01.md new file mode 100644 index 00000000..6cfb3ec4 --- /dev/null +++ b/notes/performance-moonshot/SYNTHESIS-2026-08-01.md @@ -0,0 +1,514 @@ +# Performance moonshot: final synthesis, 2026-08-01 + +This document adjudicates the twelve research documents in +`notes/performance-moonshot/` into one implementation list. Contested claims +were re-verified against the vendored Wine base (`vendor/wine-base-5c23dd1c.tar.zst`), +the vendored PipeASIO 1.2.2 source, the repository scripts, branch +`fixes/audio-hardening`, the open issue tracker, and live web sources on +2026-08-01. This is the list of record. The underlying documents remain +research inputs and are not retro-edited. + +Ranking weighs four properties per item, as agreed: certainty of the +underlying analysis, invasiveness of the change, projected performance +result with its mechanism, and chance of success. + +## 1. Corrections: claims rejected or amended + +Each entry names the claim, the verdict, the evidence, and what changes. + +**C1. "Upstream Wine has an eventfd in-process sync fallback when +/dev/ntsync is absent" (SYNC opportunity 5, MOONSHOT-OPPORTUNITIES S9). +Refuted.** In the vendored base, `inproc_wait` returns +`STATUS_NOT_IMPLEMENTED` when `inproc_device_fd < 0` and every caller then +falls through to `server_wait` (`dlls/ntdll/unix/sync.c:906`, `:2326`, +`:2352`). No eventfd path exists in the file. Consequence: the S9 spike is +deleted; the fsync fallback tier (item P7) is the only route for pre-6.14 +kernels, and the host ntsync gate (P1) matters exactly as much as the +regression note said. + +**C2. "Read wine-staging's ntdll-APC_Performance before APC work" +(OTHER-FORKS-SURVEY 2, RESEARCH 3, MOONSHOT-OPPORTUNITIES M1 step zero). +Amended.** The patchset exists in staging master, but its single patch is +"ntdll: Reuse old async fileio structures if possible", an allocation +optimization on the async I/O path. It contains nothing about APC delivery, +alertable waits, or wineserver round trips. Consequence: the step is +dropped; there is no prior art in staging for the APC fast path. + +**C3. "-DontCombineAPCs toggles Windows APC batching." Rejected as +documented fact; the local measurements stand.** Ableton's Options.txt +article (fetched via the help-center API, article 6003224107292) documents +the flag as "Deactivates the APC combination mode: won't align and sync the +session rings of multiple APCs so they can be moved independently", which +is Akai APC controller behavior. PERFORMANCE-MOONSHOT-RESEARCH.md raised +this and is vindicated. What survives: the idle thread at 30 to 40 percent +of a core was measured directly, and the A/B/A on issue 29 (fault appears +with the flag, disappears without, reappears with) is real +(`notes/ABLETON-WINE-APC-COALESCING.md`). What falls: any confidence that +the flag's mechanism is engine APC batching, and with it the specific +playback-fault story. Consequence: the `WINEDEBUG=+server` trace the APC +note already prescribes is now a hard gate before any APC patch (P5). + +**C4. "dxgi WaitForVBlank just calls Sleep(16)" (LIVE opportunity 3, +MOONSHOT-OPPORTUNITIES S4). Stale.** True of the raw base +(`dlls/dxgi/output.c:364-371`), but patch 0001 changes it to +`dxgi_sleep_for_refresh_interval()`, a refresh-rate-aware sleep. Max device +redraw is paced at the real refresh rate today, just without vblank phase +lock. Consequence: S4 is demoted from a spike to a low-priority refinement +with its factual basis corrected. + +**C5. "WINE_CPU_TOPOLOGY: confirmed" (RESEARCH technologies table). +Misleading as written.** The launcher export is real; the consumer is not. +The string appears zero times in the entire vendored Wine tree and zero +times in `patches/`; the launcher comment says "Inert on this runtime" +(`scripts/ableton-live:78-79`). Consequence: the variable does nothing +today; P8 decides port-or-delete. + +**C6. "Whether Wine maps Live's thread priorities to Linux scheduling at +all: unverified" (MOONSHOT-OPPORTUNITIES T2). Now verified.** +`server/thread.c:236-269`: NT priorities in the application band [1,15] map +to niceness via `setpriority`, only when the RLIMIT_NICE grant exists +(`nice_limit < 0`), and the realtime band is clamped to +`LOW_REALTIME_PRIORITY - 1` under the comment "FIXME: handle realtime +priorities using SCHED_RR if possible". avrt is a pure stub returning +handle `0x12345678` (`dlls/avrt/main.c`), and every rtworkq MMCSS +registration returns `E_NOTIMPL` (`dlls/rtworkq/queue.c`). Consequence: the +scheduling chain (P4) rests on verified source, not inference. + +**C7. "PipeASIO 1.2.3 is the current upstream release." Stale as of +today.** 1.2.3 (July) is build fixes only, confirmed. 1.3.0 shipped +2026-08-01: it adds a realtime audio-thread setting, bumps the WoW64 ABI to +version 3, and raises the PipeWire floor to 1.4.2. Its notes report that +multi-threaded hosts under Wine can see more xruns with the new setting on +(measured with FL Studio), and no release mentions Ableton validation. +Consequence: the vendor bump splits. 1.2.3 is the safe hygiene bump; 1.3.0 +needs evaluation and its floor conflicts with the runtime's 0.3.56 host +floor and the every-Linux-computer target. Do not adopt 1.3.0 without a +decision on the floor. + +**C8. "Live 12 needs Media Foundation work to restore video and WMA +import" (RESEARCH 8). Overstated.** Media import in Live 12 works through +winegstreamer today (`scripts/container-build.sh:96-102`); the only +recorded media crash is Live 11's `wmvcore` path, which is experimental +scope. Consequence: no Media Foundation track; Live 11 `wmvcore` stays in +the opportunistic tier. + +**C9. "Port the topology consumer to fix worker-pool oversizing; cap 8" +(SYNC 2, BUILD 7). Tempered.** ABLETON-WINE-PERFORMANCE-PLAN.md is right +that a blanket 8-CPU cap conflicts with Live's up-to-64 audio threads and +that the base counts online CPUs rather than allowed CPUs +(`dlls/ntdll/unix/system.c:1710-1731`, `_SC_NPROCESSORS_ONLN`). +Consequence: P8 ports the consumer for accurate, affinity-derived reporting +and optional pinning experiments; the 8 cap is not a default and would need +bench pairs to earn one. + +**C10. Alertable-wait cost wording in the four-review doc. Refined.** With +ntsync active, alertable handle waits do run in-kernel with a cached +per-thread alert fd (`dlls/ntdll/unix/sync.c:920`, `:872-891`). The +verified server costs are: `NtQueueApcThreadEx2` is one wineserver round +trip per queued APC (`dlls/ntdll/unix/thread.c:1796-1825`), and an +alertable sleep with no handles always round-trips the server, source +comment "if alertable, we need to query the server" +(`dlls/ntdll/unix/sync.c:2419-2431`). A loop of alertable sleeps near 1 kHz +therefore pays about 1,000 server round trips per second even with ntsync. +This strengthens P5's cost case while C3 weakens its mechanism story. + +## 2. Confirmed foundation + +Facts the list below builds on, now verified rather than cited. + +| Fact | Evidence | +|---|---| +| avrt full stub; rtworkq MMCSS all E_NOTIMPL; server maps NT priority to nice only, clamps the realtime band, FIXME on record | vendored `dlls/avrt/main.c`, `dlls/rtworkq/queue.c`, `server/thread.c:236-269` | +| Live imports AvSetMmThreadCharacteristicsW, AvSetMmThreadPriority, RTWorkQ.dll, task name "Pro Audio" | binary inspection in ABLETON-WINE-PERFORMANCE-PLAN.md, accepted | +| wine-osu carries the avrt de-stub: "Audio"/"Pro Audio" to THREAD_PRIORITY_TIME_CRITICAL plus thread naming | `9000-misc-additions/audio-thread-priority-and-name.patch` in whrvt/wine-osu-patches, fetched today | +| Without /dev/ntsync every wait is a server round trip; no intermediate tier exists | `dlls/ntdll/unix/sync.c` (C1) | +| APC queueing is a server call; alertable no-handle sleeps always hit the server | C10 | +| WINE_CPU_TOPOLOGY has no consumer anywhere; launcher counts online CPUs capped at 8 as groundwork | C5; `scripts/ableton-live:75-108` | +| No ntsync check at launch; the word does not appear in the launcher | `grep ntsync scripts/ableton-live` is empty | +| PipeASIO: silent replacement of any non power-of-two buffer with 1024; SCHED_FIFO 15 via raw `pthread_setschedparam` in its own thread-utils, no RTKit fallback; follow-device quantum machinery exists; warn-once-and-keep-feeding on quantum mismatch | `src/config.c` validate(), `include/pipeasio_config.h:54`, `src/audio.c:106-236`, `:1401-1411` | +| Branch `fixes/audio-hardening` holds the issue 49 cause table (C1 to C9) and ordered fixes F0 to F8, including F3 per-thread SCHED_FIFO wine patch, F4 ABLETON_RT default off, F5 RTKit fallback | `git show fixes/audio-hardening:notes/ABLETON-WINE-PIPEASIO-CRACKLE.md` | +| Bench harness exists with the pair protocol; no `bench/` directory or committed row exists | `scripts/bench-run.sh`, repository state | +| Whole-process SCHED_RR 10 at launch when rtprio exists; learnheal.exe resident helper; theme watcher on a 2 s loop | `scripts/ableton-live:780-783`, `:809-810`, `:437-461` | +| Issues 42, 46, 49, 63, 87, 92, 109, 111, 115 open as of today | tracker query 2026-08-01 | +| winepulse buffers three periods with a probed 10x period floor; irrelevant to the engine path (ASIO) | vendored `dlls/winepulse.drv/pulse.c:789`, `:1083` | + +## 3. Verdict per source document + +| Document | Verdict | +|---|---| +| PERFORMANCE-MOONSHOT-2026-08-01.md | Strongest single input. Its ranked structure and deliberate non-changes carry into this list. Amended by C7 (1.3.0), C10 (wording), and C3 (APC narrative now trace-gated). Its staging survey was right to omit the APC set (C2 confirms it is irrelevant). | +| MOONSHOT-OPPORTUNITIES.md | Best measurement plan and reject list; both adopted. T1, T2, T3, T4, T6 adopted. T5 demoted to one contained experiment series (P11). S9 deleted (C1), S4 demoted (C4), M1 step zero replaced (C2), S1 tempered (C9). | +| PERFORMANCE-MOONSHOT-RESEARCH.md | Mixed. The -DontCombineAPCs definitional catch (C3) is the single best correction in the whole set. Its technologies table misled on WINE_CPU_TOPOLOGY (C5), its Media Foundation item overstated (C8), and its staging backport row pointed at an irrelevant set (C2). | +| ABLETON-WINE-PERFORMANCE-PLAN.md | The binary-import evidence (avrt, RTWorkQ, "Pro Audio") is the most valuable new fact in the eight-document set; it turns the MMCSS de-stub from plausible into a certain target. Its stage gates, 8-cap warning (C9), and do-not-default list are adopted. | +| PROJECT-TECHNOLOGIES.md | Accurate inventory; every gap it lists verified. No corrections. | +| ABLETON-LIVE-TECHNICAL.md | Strong; the crash-class table seeds the regression matrix. S4 basis stale (C4). | +| PROTON-SYNC-AND-CPU.md | Good history and the host ntsync gap (P1). Its unverified eventfd flag was proper hedging; the claim is now refuted (C1). | +| PROTON-GRAPHICS-AND-RUNTIME.md | Verified Proton flags and a clean reject table; both adopted wholesale. DXVK risk analysis adopted for the parked A/B. | +| OWN-FORK-PATCH-MAP.md | Accurate map. Its central finding, sync and threading untouched by the series, underpins P4 and P5. | +| OWN-FORK-BUILD-AND-RUNTIME.md | Solid facts; compiler enthusiasm tempered into P11. The launch-gap item became P1. | +| OTHER-FORKS-SURVEY.md | Useful survey; staging APC content now known (C2). The nspa source-availability caveat becomes P9's step zero. | +| AUDIO-LATENCY-ECOSYSTEM.md | Strong host-side content: the rtkit RTTIME 200 ms trap, the scsynth core-placement lesson, per-interface buffer floors, graph-rate locking. All folded into P3 and P4 host arms. | + +## 4. Implementation list + +Ordered. Each entry states certainty (is the analysis right), invasiveness +(what it touches and can break), projected result (and the mechanism), and +chance of success (will the implementation land and hold). Every +performance claim still requires a committed before/after pair from +`scripts/bench-run.sh`; every new patch needs its build-audit fingerprint +entry. + +### P0. Baselines and harness automation + +Commit `bench/` rows for the current release before anything else, and +extend `bench-run.sh`: automated `pw-top -b` ERR capture, `pw-metadata` +rate and quantum, Live process total CPU, the busy idle thread's CPU, +startup time, and a version column set (Live, WebView2, GPU driver, +PipeWire), since WebView2 self-updates inside the prefix and has regressed +rendering before. Commit the reference set. + +- Certainty: certain. No committed row exists; the headline xrun metric is + operator-entered. +- Invasiveness: none. Tooling and data only. +- Projected result: none directly. It is the evidence floor for every other + item, and Live's CPU meter doubles as a fidelity benchmark against + Windows on the same hardware. +- Chance of success: high. +- Sources: MOONSHOT-OPPORTUNITIES T3, four-review entry 0, BUILD 2. + +### P1. Host ntsync gate + +Launch-time `[ -c /dev/ntsync ]` check with a loud warning, detection of a +kernel at or above 6.14 with the module unloaded, a `modprobe.d` drop-in or +exact load instructions in the setup scripts, and TROUBLESHOOTING coverage. + +- Certainty: high. The fallback cost is measured (about 45 percent of a + core and 9,000 context switches per second at idle; 4 to 50x probe + regression), the absence of any launcher check is verified, and C1 + removes the hope of a hidden intermediate tier. +- Invasiveness: minimal. Launcher and docs; no Wine change. +- Projected result: on affected hosts, recovers the full ntsync win that is + silently lost today. On healthy hosts, nothing changes. +- Chance of success: very high. +- Sources: SYNC 1, BUILD 1, RESEARCH 1, MOONSHOT-OPPORTUNITIES T1. + +### P2. Scheduling A/B + +Run the written 4-CPU protocol from `notes/ABLETON-WINE-RT-SCHEDULING.md` +with four arms: default RR 10, `ABLETON_RT=off`, wineserver `chrt -f` +boost, and realtime narrowed to audio threads once P4 provides it. Record +pairs on a low-core machine and a many-core machine. + +- Certainty: the protocol exists and the three risk hypotheses are + recorded; the outcome is unknown, which is the point. +- Invasiveness: none. Measurement only. +- Projected result: decides whether whole-process RR stays the default + (F4), whether wineserver needs a priority floor, and quantifies the + priority-inversion shape (Live RT threads calling a SCHED_OTHER + wineserver) that the crackle analysis lists as cause C3. +- Chance of success: high. +- Sources: RT note, MOONSHOT-OPPORTUNITIES T2, AUDIO 1, SYNC 3. + +### P3. Audio hardening, driver track + +Execute F0 to F8 from `fixes/audio-hardening`, merged with the host-side +audio items: F0 capture tooling and corrected warning text first, then +quantum convergence (follow the graph cycle, accept non power-of-two sizes, +stop silently replacing invalid configuration with 1024), correct latency +reporting with the latency-changed callback, single-clock duplex default, +priority-inheritance mutexes on shared driver state, a stall watchdog on +the JACK eviction model, and the PipeASIO 1.2.3 hygiene bump. Host arm: +per-interface buffer floor guidance (USB near 512, HDA 128 to 256), a +defined policy for PipeWire hosts older than 1.6 (require or ship a +`clock.force-quantum` fallback), and graph-rate locking so PipeWire never +resamples Live. Evaluate 1.3.0 separately under C7. + +- Certainty: high. Issue 49's mechanism is confirmed (forced-quantum + recency arbitration, wrong-speed playback), the cause table and fix order + are written, and the driver code paths were re-verified in source. +- Invasiveness: medium. Driver patches, launcher, setup, docs; it is the + audio path, but the plan's own order puts capture tooling before any + behavior change, and the three-distribution verification matrix exists. +- Projected result: closes the open audible-defect classes (wrong-speed + playback, crackle under quantum contention, duplex resync crackle) and + makes recording offsets correct, since Live derives them from reported + latency and the driver currently reports a fixed one-buffer guess. + In DAW terms this is the performance result that users hear. +- Chance of success: high. The plan, risk table, and regression list are + already written; issue 49 is reproduced and understood. +- Sources: fixes/audio-hardening plan, four-review entry 3, + MOONSHOT-OPPORTUNITIES T4, AUDIO 2, 6, 7. + +### P4. Thread-priority chain, scheduler track + +Restore Live's priority structure in order: (a) de-stub avrt so +"Pro Audio" registrations get time-critical priority, starting from the +wine-osu patch, which under the current server means best-nice within the +host grant; (b) implement the `server/thread.c` FIXME, mapping the Windows +realtime band to per-thread SCHED_FIFO or SCHED_RR behind an environment +gate, budgeted under the host rtprio grant and placed below PipeASIO's FIFO +15 and PipeWire's data threads; (c) only after P2 pairs exist, retire +whole-process RR as the default (F4); (d) add the RTKit fallback in +PipeASIO (F5) last, respecting the RTKit RTTIME 200 ms budget trap and its +priority cap of 20. Host arm: extend `setup-realtime.sh` into a full audit +(governor, threadirqs and rtirq, RTTIME limits, rtkit presence on GNOME 45 +and newer, PipeWire rt.prio), failing the realtime check instead of +printing advice once. + +- Certainty: high on the problem. Every link is verified: Live imports the + MMCSS APIs and the "Pro Audio" task name, avrt and rtworkq are stubs, the + server clamps the realtime band with a FIXME, and whole-process RR + flattens audio against UI. Medium on the size of the gain: nobody ships + this; wine-nspa proves the model on Live with self-reported numbers. +- Invasiveness: high. wineserver, ntdll, avrt, launcher, driver, and host + policy; a wrong priority order can starve the desktop or the audio + callback. The environment gate and the P2 protocol are the containment. +- Projected result: the audio path outranks the interface path for the + first time, per thread, which is what Live's manual expects and what the + whole-process wrapper cannot express. Mechanism: dropout margin at small + buffers is set by worst-case wake-up latency of a few hot threads; + per-thread realtime placement directly cuts that tail, and wine-nspa's + Live measurements (condition-wait worst case 263 to 152 microseconds + with PI locks; futex wait halved under mlockall) indicate the size class. + Expect the largest effect at 64 to 128 frames and under UI load. +- Chance of success: medium-high. The avrt half is a small proven patch; + the server half is a bounded change at a marked FIXME; the ordering + constraints (never remove whole-process RR first, wineserver never above + the callback by default) are written down. +- Sources: four-review entry 1, PLAN stage B, F3 to F5, FORKS 4, wine-osu + patch, AUDIO 5, MOONSHOT-OPPORTUNITIES S5. + +### P5. The busy idle thread: trace, then the fast path + +Step one is the `WINEDEBUG=+server` trace of an idle session that the APC +note prescribes: identify the busy thread's actual loop (alertable sleeps, +handle waits, queued APCs, message waits) and count request types. C3 makes +this non-optional: the flag that anchored the coalescing story is an Akai +controller option, so the loop's shape is unknown until traced. Step two, +if the trace confirms same-process APC or alertable-sleep churn: implement +the client-side fast path through the ntsync alert event (same-process user +APCs to a client queue, drain before server APCs, cross-process and special +APCs stay server-side), plus, if the trace shows it, a client-side path for +alertable zero-handle sleeps. Ship an `apcprobe` for FIFO order, +NtTestAlert, special APCs, and I/O completion ordering before the patch. + +- Certainty: high on the cost, medium on the mechanism. The 30 to 40 + percent idle core is measured; the per-call server round trips for APC + queueing and alertable sleeps are verified in source (C10); which of them + Live's loop actually exercises is unproven (C3). +- Invasiveness: high. Core ntdll wait and APC semantics with strict + ordering rules; a mistake corrupts playback subtly. +- Projected result: idle CPU for the busy thread from 30 to 40 percent to + under 5 percent, and a large cut in wineserver context switches under + load. Mechanism: the loop pays about one server round trip per + millisecond today; the alert event already exists per thread, so + same-process delivery can skip the server entirely. +- Chance of success: medium. The design sketch and verification list exist; + the trace may redirect the work (a finding that the loop is message waits + or timer churn would send this elsewhere). The trace itself is cheap and + cannot fail to inform. +- Sources: APC note, four-review entry 2, SYNC 4, LIVE 1, + MOONSHOT-OPPORTUNITIES M1, C2, C3, C10. + +### P6. Present path finishing + +Stop the perpetual 200 ms reblit timers while panes are hidden and re-arm +on show; make visible-pane reblits event-driven instead of the 5 Hz tick +that copies identical content; lift the WS_POPUP exclusion from the GL +present path by fixing the first-map black frame; A/B the launcher's forced +full-redraw default; port wine-nspa's X11 flush throttle and vectorized +surface copy for windows that stay on the CPU path. Fold in PLAN's framing: +prefer adopting giang17's newer composition work over adding more timers, +which also feeds the parked 11.14 base bump. + +- Certainty: high on the waste (30 of 30 identical-content full-pane copies + hashed at 5 Hz; popups and children still pay the copy path that cost + about a core before patch 0055). Medium on how much CPU returns. +- Invasiveness: medium-high. dxgi and winex11; the steady reblit is what + keeps WebView2 panes composited (patch 0041), so removal needs the + damage-counter and frame-hash evidence at 100, 125, and 200 percent + scale, plus Learn View, sidebar, and Splice open-close-reopen runs. +- Projected result: idle pane damage near zero, lower CPU while dragging in + dialogs, and the remaining full-frame copies (Settings, auth dialog, + plugin editors) move to the GL path. Mechanism: delete recurring copies + of unchanged pixels; the same class of change already measured 650 MB/s + to 0.4 MB/s on the main window. +- Chance of success: medium-high. The black-popup first-map issue is the + known hard part. +- Sources: four-review entry 4, LIVE 4, MOONSHOT-OPPORTUNITIES S3, PLAN + stage E, PATCHMAP. + +### P7. Sync coverage and trust + +Root-cause issue 109 (ntsync WAIT_ANY returning instantly in a spin; +18,700 waits per second in an installer) and add a livelock case to the +ntsync probe. Port the maintained fsync fallback (Gofman rebase in the +wine-osu line) so kernels older than 6.14 get futex sync instead of full +server round trips, pick order ntsync, fsync, server. Add an ntsync +off-switch environment variable mirroring PROTON_NO_NTSYNC for A/B runs. + +- Certainty: high that the tier is missing (C1 verified the fallback is + full server). The fsync rebase's current state on our exact base is + unverified; treat the port as needing its own validation. +- Invasiveness: medium. ntdll and server patches on well-trodden Proton + code paths, plus probe and docs. +- Projected result: pre-6.14 hosts recover most of the sync win (fsync is + roughly equal to ntsync in throughput with weaker semantics), and the + issue 109 class stops shipping silently. +- Chance of success: medium-high. +- Sources: four-review entry 5, SYNC, FORKS, issue 109. + +### P8. Topology and placement + +Decide the WINE_CPU_TOPOLOGY consumer: port Proton's (about 200 +self-contained lines, prefers physical and performance cores) or delete the +launcher export. If ported, base reported CPUs on `sched_getaffinity` +rather than online count, per PLAN. Verify which Windows version the prefix +reports, since Live restricts audio to performance cores only when it sees +Windows 11. Publish hybrid-CPU pinning guidance and run pinned versus +unpinned pairs on a P/E-core machine. No 8-CPU cap by default (C9). + +- Certainty: high on the current inertness and the wrong counting basis; + medium on gains, which are hardware-dependent. +- Invasiveness: medium. One ntdll patch plus launcher policy; reporting + changes affect Live's own thread placement decisions. +- Projected result: on hybrid CPUs, audio workers stop landing on + efficiency cores. The scsynth precedent showed a 40 to 50 percent CPU + swing from placement alone, erased by pinning; that is the size class at + stake on affected machines. +- Chance of success: medium-high for the port; the policy needs hardware to + prove. +- Sources: SYNC 2, PLAN stage C, MOONSHOT-OPPORTUNITIES T6/S1, AUDIO 4, + C5, C9. + +### P9. Memory and locality + +Step zero: verify wine-nspa 11.x sources or patch files are actually +available; the survey could not confirm it. Then, gated behind the existing +realtime switch: `mlockall` with on-fault locking, the +thread-environment-block hot-state patch, and the two heap triage switches +(delayed free, zeroed free) as documented off-by-default tools for plugin +crashes. + +- Certainty: medium. The numbers are wine-nspa's own Live measurements + (futex wait 94 to 49 microseconds, 14.3 percent cycle cut, 20 percent + fewer page faults) and are whole-stack, not per patch. +- Invasiveness: medium. ntdll and launcher; mlockall interacts with + multi-GB sample buffers, so lock on-fault only and measure memory + pressure, per PLAN's warning against broad locking. +- Projected result: lower worst-case wait latency and fewer faults during + playback, which is deadline-tail insurance rather than average speed. +- Chance of success: medium. +- Sources: four-review entry 6, FORKS 1, MOONSHOT-OPPORTUNITIES M2, PLAN. + +### P10. Timing fidelity + +Confirm the Live-version match first (the top hypothesis for issue 101's +0.08 percent ramp delta), then run the deferred export comparisons (buffer +64 versus 2048, and 192 once P3 makes it legal). Review the community QPC +patch and the TSC evidence before deciding whether QueryPerformanceCounter +should change. Measure MIDI output jitter against the 1 ms timer. + +- Certainty: low-medium. The delta is real and unexplained; per-buffer + quantization is quantified insufficient. +- Invasiveness: low for measurement; a QPC change is medium and only + follows evidence. +- Projected result: fidelity and MIDI timing, not throughput. +- Chance of success: medium. +- Sources: four-review entry 8, issue 101 findings note, + MOONSHOT-OPPORTUNITIES S12. + +### P11. Compiler flag pairs, one contained series + +Resolve the T5-versus-non-change conflict by running one bounded experiment +series and then closing the question either way: Proton's baseline +(`-O2 -fwrapv -fno-strict-aliasing -march=nocona -mtune=core-avx2 +-mfpmath=sse`), `-O3`, and an `-march=x86-64-v2` variant, each through the +relocation gate, the build audit, and a bench pair. Ship a change only on a +measured win. The default artifact keeps a generic-to-v2 floor because one +tarball serves every machine including Live 11 hosts; a v3 build could only +ever be a separate opt-in artifact. ThinLTO and PGO stay parked as spikes +behind this gate; no surveyed fork ships either for Wine. + +- Certainty: high that the expected gain is small. Every hotspot found in + this research is algorithmic, and most session CPU burns in Live.exe and + plugins, which no Wine flag touches. +- Invasiveness: low. Build-only, fully audited, reversible. +- Projected result: likely noise to low single digits on Wine-side + metrics; the value is closing the question with pairs instead of + repeated debate. +- Chance of success: builds likely succeed; wins uncertain by design. +- Sources: GFX 1, BUILD 4/6, FORKS 3, four-review deliberate non-changes, + MOONSHOT-OPPORTUNITIES T5. + +### P12. Parked strategic tracks + +In rough order of readiness, all blocked on the items above or on upstream +motion: + +- winepipewire.drv port for every non-ASIO audio path in the prefix; + default-on in proton-cachyos; after P3. +- DXVK, with the wined3d Vulkan backend as the cheaper first A/B. + Prefix-level, no rebuild. Abandon criteria are fixed in advance: Live's + device-name gate must pass, WebView2 panes and GL plugin editors must not + regress, and it must beat the tuned GL present path in pairs, which + patches 0055/0058/0059 would no longer cover under Vulkan. +- Base bump to giang17 d2d1-dcomp-11.14 with the rebase narrative pattern + from the 11.11 to 11.13 bump; brings a winegstreamer stride fix and a + winewayland deadlock fix, and is where P6's adopt-newer-composition idea + lands. +- wine-nspa message rings and local objects, after P9's source + verification; known Live library-panel regression risk near our patches + 0018/0019. +- PipeASIO 1.3.0 evaluation (C7): floor decision first. +- A Linux-plugin bridge on the yabridge model, after the engine path is + stable. +- Link Audio: measurement and documentation arm only (S13); implementation + is out of scope until demand and feasibility are shown. + +### Opportunistic small items + +Cheap, independent, take when passing: + +- Land the parked cross-process visible-region fix (patch 0046 on + `fixes/issue-57-crossproc-visrgn`), then retest Splice input death + (issue 87). +- Port staging `server-Signal_Thread` (thread-termination race) and + GE-Proton's winepulse timestamp-wrap recovery for long sessions. +- Check the fractional-scaling DPI override against WebView2's refusal to + initialize on DPI-awareness mismatch. +- Diff GE-Proton's GPU-description patch against 0057/0061. +- `wineserver -p` persistence or skip-reboot for launch feel (no + steady-state audio effect). +- Document hard links for plugin folders (the scanner skips reparse + points), the `/dev/ntsync` requirement, and the forced-quantum symptom. +- Test and document the Carla external-host workflow for unstable + Linux-native plugins. +- Wine file-I/O streaming benchmark against the disk-overload path. +- THP `madvise` versus `never` experiment on a large session. +- M4L font-API audit for Windows-lax behaviors Max relies on. +- Live 11 `wmvcore` export identification (experimental scope). +- WineASIO-versus-PipeASIO comparison close-out (evidence hygiene only). + +## 5. Do not pursue + +Merged decision of record from all twelve documents, upheld here: esync or +fsync as the primary sync path (fsync appears only as the P7 fallback +tier); wineserver replacement; vkd3d-proton; DXVK-NVAPI; Fossilize; +pressure-vessel or any runtime container; gamescope; GE-Proton as a +runtime; allocator swaps; `-DontCombineAPCs` in any form (C3 adds that it +is not even the flag the lore thought it was); a blanket PREEMPT_RT +recommendation (S15's measured evaluation stands); wine-wayland migration +this year; timer-resolution de-stubbing; staging DirectComposition patches +into this base, ever; hangover and box64. Whole-process realtime stays the +default until P2 and P4 produce the evidence to retire it. + +## 6. Method note + +Verification used: extraction and reading of the vendored Wine base and +PipeASIO 1.2.2 sources; repository scripts and branch +`fixes/audio-hardening`; the open issue tracker on 2026-08-01; and live +fetches of the wine-staging tree listing, Ableton's Options.txt article via +the help-center API, PipeASIO releases (1.2.3 and the same-day 1.3.0), and +the wine-osu avrt patch. Claims from wine-nspa remain self-reported and are +marked as such wherever they set expectations. diff --git a/notes/performance-moonshot/docs/ABLETON-LIVE-TECHNICAL.md b/notes/performance-moonshot/docs/ABLETON-LIVE-TECHNICAL.md new file mode 100644 index 00000000..4c407956 --- /dev/null +++ b/notes/performance-moonshot/docs/ABLETON-LIVE-TECHNICAL.md @@ -0,0 +1,222 @@ +# Ableton Live internals for Wine optimization + +This document explains how Ableton Live works technically at the level needed to choose and implement Wine-side performance and stability optimizations. It mines this repository's notes and patch series, then fills gaps with Ableton's documentation and public bug reports. Each section ends with the implication for Wine performance or stability. + +Scope: Live 12 is the production target. Live 11 support is experimental (`notes/ABLETON-WINE-LIVE11-WMVCORE-STUB.md:3-4`). The tested production version across the notes is Live 12.4.3 (for example `notes/ABLETON-WINE-DPI-SCALE-100.md:5`). + +Terms used throughout: ASIO (Audio Stream Input/Output) is Steinberg's low-latency audio driver interface that Live uses on Windows instead of the default MME/DirectX path (https://help.ableton.com/hc/en-us/articles/209072289-How-to-reduce-latency). PipeWire is the Linux audio server this project targets through PipeASIO. An xrun is a buffer underrun or overrun, heard as a click or dropout. + +## Which Live version is which + +Differences that matter for Wine work: + +| Property | Live 11 | Live 12 | Evidence | +|---|---|---|---| +| Bitness | 64-bit only (plugins 64-bit since Live 10.1) | 64-bit only | https://help.ableton.com/hc/en-us/articles/209071729-Using-VST-plug-ins-on-Windows; `notes/ABLETON-WINE-PIPEASIO.md:22-23` | +| Media import/preview | Loads Windows Media Format (`wmvcore.dll`); crashes on Wine stubs | Does not use this path | `notes/ABLETON-WINE-LIVE11-WMVCORE-STUB.md:3-6` | +| Bundled Max | Max 8 | Max 9 (release notes record bundled build 9.0.9) | `notes/ABLETON-WINE-LIVE11-WMVCORE-STUB.md:85`; https://www.ableton.com/en/release-notes/live-12/ | +| GPU renderer | None | Hardware-accelerated GPU renderer since 12.2, off by default | https://help.ableton.com/hc/en-us/articles/4405388230674-Recommended-Graphics-Settings-Windows | +| Splice integration | None | In-browser Splice view since 12.3 (WebView2 pane) | https://www.ableton.com/en/blog/live-12-3-is-here/; `notes/ABLETON-WINE-GPU-RENDERER.md:20-21` | +| Stem Separation | None | On-device AI stem separation since 12.3 (Suite) | https://www.ableton.com/en/blog/live-12-3-is-here/ | +| Link Audio | None | Network audio streaming between devices since 12.4 | https://www.production-expert.com/production-expert-1/ableton-live-124-released-link-audio-updated-devices-and-move-and-note-20 | +| Installer extras | `tlsetupfx.exe` USB driver installer can fault under Wine | Same class of helper; not re-confirmed per release | `notes/ABLETON-WINE-LIVE11-WMVCORE-STUB.md:62-64` | + +## Audio path + +How Live produces sound: + +- Live processes audio in fixed-size buffers. The buffer size is set in Live's Audio Preferences; smaller buffers lower latency but need more CPU, and too-small buffers cause dropouts (https://help.ableton.com/hc/en-us/articles/209072289-How-to-reduce-latency). +- Live's CPU meter is not a CPU-usage meter. It compares the time needed to process one buffer with the time one buffer takes to play. Over 100 percent means a missed deadline and a dropout (https://www.ableton.com/en/manual/computer-audio-resources-and-strategies/). +- The engine is multithreaded. Live assigns independent signal-path segments to separate threads, supports up to 64 cores and 64 audio threads, enables hyper-threading automatically, and on hybrid CPUs runs audio only on performance cores (https://help.ableton.com/hc/en-us/articles/209067649-Multi-core-performance-in-Ableton-Live-FAQ). Serial dependencies (one track feeding another, sidechains, sends) force in-order processing on the critical path, so single-core speed still bounds heavy chains. +- Live's manual states: Live expects the audio thread to have the highest priority, but the OS makes the final scheduling decision (https://www.ableton.com/en/manual/computer-audio-resources-and-strategies/). +- Moving audio to and from the hardware is a constant CPU drain proportional to active channels; Live keeps unused channels enabled to avoid a driver reconfiguration hiccup (https://www.ableton.com/en/manual/computer-audio-resources-and-strategies/). +- On Windows, low latency requires ASIO. Under this project the ASIO driver is PipeASIO, a Wine ASIO driver that exposes Live as a native PipeWire client and removed JACK from Live's audio path in release 2026.07.17.2 (`notes/ABLETON-WINE-PIPEASIO.md:3-4`). +- PipeASIO defaults: two inputs, two outputs, a fixed 256-frame buffer, automatic connection, graph-rate following (`notes/ABLETON-WINE-PIPEASIO.md:57-59`). On PipeWire 1.6 or newer it can match the graph quantum to the ASIO buffer; a 256-frame configuration produced `force-quantum` 256 in validation (`notes/ABLETON-WINE-PIPEASIO.md:20`, `notes/ABLETON-WINE-PIPEASIO.md:73`). +- The project's two PipeASIO patches clamp unsupported sample-rate requests (keeping the graph rate instead of failing with `ASE_NoClock`) and report the clock Live uses for MIDI timestamps (`notes/ABLETON-WINE-PIPEASIO.md:28-30`, `patches/pipeasio/0001-asio-keep-graph-sample-rate-instead-of-ASE_NoClock.patch`, `patches/pipeasio/0002-asio-report-timeGetTime-in-ASIO-systemTime.patch`, `notes/ABLETON-WINE-AUDIO-HOTPLUG.md:35-37`). +- Validation recorded about 8 percent Live DSP load at 48 kHz and 256 frames, not a controlled latency comparison (`notes/ABLETON-WINE-PIPEASIO.md:83-85`). +- Scheduling: the launcher starts Wine under `SCHED_RR` priority 10 whenever `chrt -r 10 true` succeeds; PipeASIO separately requests `SCHED_FIFO` priority 15 for its data-loop thread (`notes/ABLETON-WINE-RT-SCHEDULING.md:3-6`, `scripts/ableton-live:780-782`). `ABLETON_RT=off` disables only the launcher's policy. +- MIDI arrives through Wine's ALSA sequencer driver (`winealsa.drv`). Wine enumerates sequencer ports once per process; patch 0028 re-subscribes when a device returns, but a device first connected after startup still needs a Live restart (`notes/ABLETON-WINE-MIDI-HOTPLUG.md:3-5`, `notes/ABLETON-WINE-MIDI-HOTPLUG.md:13-17`). +- Audio device enumeration goes through MMDevice. A stopped WirePlumber can leave only `auto_null` and block enumeration after `Audio In Out: Constructor finished`; a Wine bug re-wrapped stored endpoint names until enumeration failed (patch 0021) (`notes/ABLETON-WINE-AUDIO-CRASH-BUG.md:3-5`, `notes/ABLETON-WINE-AUDIO-CRASH-BUG.md:22-36`). +- Tempo automation timing: issue 101 reports tempo ramps render about 0.33 seconds shorter over a 7-minute export than on Windows (0.08 percent). Per-buffer tempo evaluation was simulated and is too small to explain it; the leading hypothesis is different Live versions on the two installs. Unresolved (`notes/FINDINGS-TEMPO-RAMP-2026-07-31.md:20-24`, `notes/FINDINGS-TEMPO-RAMP-2026-07-31.md:110-131`). + +Synchronization load, measured on this stack: + +- Live batches engine asynchronous procedure calls (APCs, Windows callbacks queued to a specific thread) with a high-frequency alertable wait. The coalescing thread used 30 to 40 percent of one core while idle. Disabling coalescing with `-DontCombineAPCs` removed that load but caused choppy, slowed playback (issue 29); the option was removed again (`notes/ABLETON-WINE-APC-COALESCING.md:2-7`). +- The unconfirmed hypothesis: a wait loop near 1 kHz, and with coalescing off, one wineserver round trip per engine APC, serialized by the single-threaded wineserver (`notes/ABLETON-WINE-APC-COALESCING.md:14-28`). +- ntsync (the Linux kernel driver for NT synchronization primitives) matters measurably: a build that omitted it pushed wineserver to about 45 percent of one core and about 9,000 context switches per second with Live idle at a 256-frame buffer; restoring it gave 4 to 50 times more synchronization throughput in probes (`notes/ABLETON-WINE-NTSYNC-REGRESSION.md:11-14`). + +Implication for Wine: + +- The audio thread's deadline is a few milliseconds (256 frames at 48 kHz is about 5.3 ms). Every synchronous wineserver call on an audio-pool thread is a deadline risk; ntsync coverage and any remaining server round trips in the APC path are the highest-leverage audio-stability targets (`notes/ABLETON-WINE-APC-COALESCING.md:31-47`). +- Unverified: whether Wine maps Live's Windows thread-priority requests to Linux scheduling at all, and how that interacts with the launcher's blanket `SCHED_RR`. The scheduling note lists untested hypotheses: Linux's 950 ms/s realtime throttle, all inherited threads sharing RR 10, and Live's realtime threads outranking the `SCHED_OTHER` wineserver they make synchronous calls to (`notes/ABLETON-WINE-RT-SCHEDULING.md:30-42`). +- Buffer size is the user's latency knob end to end: Live buffer, PipeASIO `PIPEASIO_PREFERRED_BUFFERSIZE`, PipeWire quantum. The chain already works (force-quantum follows the ASIO buffer), so Wine work here is validation, not plumbing (`notes/ABLETON-WINE-PIPEASIO.md:87-96`). +- Export timing (tempo ramps) is clock-independent — export reads no audio device clock — so any remaining render difference points at math or engine evaluation order, not at scheduling (`notes/FINDINGS-TEMPO-RAMP-2026-07-31.md:36-41`). + +## Plugin hosting + +How Live hosts plugins: + +- Live supports VST2 and VST3 on Windows, 64-bit only (https://help.ableton.com/hc/en-us/articles/209071729-Using-VST-plug-ins-on-Windows). +- Live does not sandbox plugins. They load into Live's process; a plugin crash can take Live down, and plugin sandboxing is a long-standing feature request (https://forum.ableton.com/viewtopic.php?t=244536). Crash reports from plugin vendors show a plugin fault crashing the whole DAW on native Windows (for example https://forum.vital.audio/t/vital-crashes-ableton-live-11-windows-10/5634). +- Repo evidence agrees: plugin editors are child or tool windows of Live's own window tree, class `Vst3PlugWindow`, style `WS_EX_TOOLWINDOW`, in a different DPI space than Live's main window (`notes/ABLETON-WINE-PLUGIN-TITLEBAR-BUG.md:12-18`). The WebView2 plugin-close crash happened inside Live's process in `ole32` (`notes/ABLETON-WINE-WEBVIEW2-PLUGIN-CLOSE-CRASH.md:16-19`). +- Plugin GUI technologies seen in this repo's notes, with the Wine surface each one exercises: + +| Plugin / framework | GUI technology | Wine surface hit | Evidence | +|---|---|---|---| +| JUCE 8 (SWAM, Pianoteq, many vendors) | Direct2D via DirectComposition; DropShadower layered windows | dcomp subclassing, layered attributes, mixed-DPI hosting | `notes/ABLETON-WINE-INPUT-BUG.md:9-24`; `notes/ABLETON-WINE-PLUGIN-TITLEBAR-BUG.md:40-45` | +| CHOW Tape Model (JUCE) | OpenGL child surface on depth-32 ARGB window | XRender pict format depth matching | `notes/ABLETON-WINE-GL-PLUGIN-EDITOR-CRASH-BUG.md:1-14` | +| nih-plug / baseview (Rust) | wgl pixel format with `srgb: true` | EGL backend sRGB formats; a miss aborted Live through a Rust panic | `notes/ABLETON-WINE-INPUT-BUG.md:61-68` | +| Splice INSTRUMENT | WebView2 editor | cross-process OLE drop-target revocation | `notes/ABLETON-WINE-WEBVIEW2-PLUGIN-CLOSE-CRASH.md:3-18` | +| Pianoteq (JUCE) | DPI-unaware editor hosted under Auto-Scale | host-driven resize negotiation loop | `notes/ABLETON-WINE-PIANOTEQ-DPI-GHOST-BUG.md:9-18` | + +- Live's Auto-Scale Plugin Window is enabled by default and Ableton recommends leaving it on (https://help.ableton.com/hc/en-us/articles/4405388230674-Recommended-Graphics-Settings-Windows). Disabling it was the workaround for the Pianoteq resize loop (`notes/ABLETON-WINE-PIANOTEQ-DPI-GHOST-BUG.md:3-5`). +- Delay compensation: plugins and devices can add latency; Live compensates across tracks. An open Max for Live editor adds latency (https://help.ableton.com/hc/en-us/articles/209072289-How-to-reduce-latency). +- Linux-native plugin routing (Carla over PipeWire, Carla's Wine-native bridge, winesulin) is documented but untested by this project (`notes/ABLETON-WINE-PLUGIN-BRIDGING.md:1-6`, `notes/ABLETON-WINE-PLUGIN-BRIDGING.md:46-54`). + +Implication for Wine: + +- Because hosting is in-process, every Wine defect a plugin framework hits becomes a Live defect. Plugin GUI bugs dominated this project's patch series (patches 0014-0026, 0045; `patches/BASE.txt` ledger and `notes/ABLETON-WINE-INPUT-BUG.md:1-7`). +- There is no plugin-process boundary to absorb Wine regressions. A Wine change that breaks one framework's pixel-format, dcomp, or OLE path ships straight into Live's serious-program-error dialog. The repo's probe suite (`tools/glchild.c`, `tools/webviewclose.c`, `tools/fakeplugin.c`) exists precisely because of this (`notes/ABLETON-WINE-INPUT-BUG.md:73-74`, `notes/ABLETON-WINE-WEBVIEW2-PLUGIN-CLOSE-CRASH.md:31-43`, `notes/ABLETON-WINE-PLUGIN-TITLEBAR-BUG.md:27-28`). +- Mixed-DPI hosting is structural, not incidental: Live computes plugin editor insets in its own DPI space while the editor lives in another (`notes/ABLETON-WINE-PLUGIN-TITLEBAR-BUG.md:25-28`). Any present-path or resize-path change must be tested at fractional and 2x scales, per the GPU renderer note's open checks (`notes/ABLETON-WINE-GPU-RENDERER.md:58-60`). + +## Graphics and UI + +How Live draws: + +- Live's UI is its own framework. A repo note identifies it as Skia-based (`notes/FINDINGS-LIVE-THEME-PREVIEW-SIGNAL-2026-07-26.md:10`). On Windows, Live 12.2 added a hardware-accelerated GPU renderer, off by default, enabled in Settings → Display & Input (https://help.ableton.com/hc/en-us/articles/4405388230674-Recommended-Graphics-Settings-Windows). +- On Windows the GPU renderer is Live's Direct2D/Direct3D 11 path; the fallback is a GDI renderer, forced by `-_ForceGdiBackend` in `Options.txt` (`notes/ABLETON-WINE-GPU-RENDERER.md:8-13`). This project removed that flag and runs the GPU renderer (`notes/ABLETON-WINE-GPU-RENDERER.md:8-17`). +- Live gates the GPU renderer on the reported graphics device. It reads the device name and PCI ID from the API (wined3d's device table under Wine) and rejects unknown devices with the message "Intel(R) HD Graphics 4000: Unexplained slow UI at zoom-level 100% and/or crashes". Patches 0057 and 0061 keep current Intel and Arc devices past that check (`notes/ABLETON-WINE-GPU-RENDERER.md:175-198`). +- Measured effects of the GPU renderer under this Wine: idle CPU drops from about 59 percent of one core to 1-2 percent, and the WebView2 pane flicker stops (`notes/ABLETON-WINE-GPU-RENDERER.md:19-29`). +- Present path: before patch 0055, Wine copied every finished main-window frame from the graphics card to main memory and sent it to the display server as a full-window image, about 14 MB per frame at 2560x1350, about 650 MB per second during continuous UI activity (`notes/ABLETON-WINE-GPU-RENDERER.md:64-73`). Patch 0055 presents top-level windows directly with `glXSwapBuffers`; `WS_CHILD` windows (embedded plugin editors) and `WS_POPUP` windows (Settings, the authorization dialog, context menus) keep the copy path (`notes/ABLETON-WINE-GPU-RENDERER.md:75-86`). Patches 0058/0059 fix a DPI-context disagreement that landed direct-path frames too low under fractional scaling (`notes/ABLETON-WINE-GPU-RENDERER.md:99-171`). +- Live embeds WebView2 (Microsoft's Chromium browser control) for the Learn View, documentation sidebar, and Splice view. Live hardcodes `--disable-gpu --disable-gpu-compositing --disable-direct-composition` into its browser processes, so WebView2 renders in software (`notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md:15-18`). The Evergreen runtime auto-updates inside the prefix; version 149 introduced delegated compositing, which broke rendering on the base fork (https://github.com/giang17/wine/issues/8, `notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md:93-100`). The same present-but-never-composited failure class exists on native Windows (https://github.com/MicrosoftEdge/WebView2Feedback/issues/5574). +- Live's menu bar and Preferences dropdowns are different window types. The menu bar uses Win32 `#32768` menus drawn by win32u; Preferences lists are Live-owned `WS_POPUP` windows (`notes/ABLETON-WINE-DROPDOWN-MANAGED-FLIP.md:19-26`). Wine chrome (menu bar, dialogs) is themed to match Live through registry colors and a `SetSysColors` watcher (`notes/ABLETON-WINE-MENU-COLOR-THEMING.md:19-28`). +- Live is DPI-sensitive in a specific way: it leaves the process default DPI-unaware, selects per-monitor-v2 on individual threads, and recalculates its outer rectangle from the client area after every `ConfigureNotify`. Mixed DPI states caused layout loops of 175 to 400 no-op `SetWindowPos` calls per second at 80 to 99 percent of one core (`notes/ABLETON-WINE-RESIZE-BUG.md:9-35`, `notes/ABLETON-WINE-DPI-SCALE-100.md:62-66`). The launcher manages this with a per-executable IFEO `dpiAwareness` value and prefix calibration (`notes/ABLETON-WINE-RESIZE-BUG.md:52-68`, `notes/ABLETON-WINE-DPI-SCALE-100.md:36-43`). +- Live's Settings UI touches no file, registry key, or other observable channel while it live-previews a theme; `Preferences.cfg` is written only when the dialog closes (`notes/FINDINGS-LIVE-THEME-PREVIEW-SIGNAL-2026-07-26.md:42-49`). + +Implication for Wine: + +- UI rendering is GPU-bound work travelling over Wine's d2d1/wined3d/dcomp stack — the reason this fork's base is giang17's d2d1-dcomp branch (`patches/BASE.txt:3-6`). The remaining known costs are the copy path for `WS_POPUP` and `WS_CHILD` windows and any frame that fails the 0058 agreement gate (`notes/ABLETON-WINE-GPU-RENDERER.md:78-86`, `notes/ABLETON-WINE-GPU-RENDERER.md:161-165`). +- WebView2 is a second compositor inside the process, in software mode, versioned outside this project's control. An Evergreen update can regress the Learn and Splice views without any change in Wine or Live; the launcher records the WebView2 version for this reason (`notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md:186-189`). +- DPI agreement between threads is a recurring root cause (resize loop, present-path black band). Patches 0023 and 0059 bracket rect queries in the target window's DPI context; any new present or resize code needs the same discipline (`notes/ABLETON-WINE-GPU-RENDERER.md:133-156`, `notes/ABLETON-WINE-PIANOTEQ-DPI-GHOST-BUG.md:31-37`). + +## Max for Live + +How Max for Live works here: + +- Max for Live (M4L) runs Cycling '74 Max devices (`.amxd` files) inside Live through `MaxPlug.dll`, loaded into Live's process. The hang evidence shows Max modules (`maxplug`, `patcher`, `jsui.mxe64`) on Live's own thread stacks (`notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:53-80`). +- Devices are authored on macOS and request macOS typefaces (Geneva, Menlo, Lucida Grande, Helvetica Neue) plus Consolas. On Windows, GDI font mapping never fails, so Max's fallback chain is never exercised. Under Wine the lookup fails, Max walks its hardcoded chain ending at Bitstream Vera faces, and if those are also missing, MaxPlug parks Live's UI thread on a condition variable forever. The audio pool keeps playing (`notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:12-41`, `notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:61-63`). +- Fix shipped: the installer vendors Bitstream Vera into the prefix and registers each face; both halves are required because Wine's font list is registry-driven (`notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:104-128`). `scripts/check-m4l-fonts.sh` guards the chain. +- Max renders its device UI through Direct2D/dxgi. During the deadlock investigation, Max's renderer sat in `dxgi_output_WaitForVBlank`, a Wine semi-stub that only calls `Sleep(16)` (`notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:86-88`). +- M4L device windows come and go as Live children when tracks are selected. That visibility change flipped Wine's whole client surface between attached and offscreen-composited paths, flashing the window black; patch 0062 keeps the Live class on the offscreen path (`notes/ABLETON-WINE-M4L-SELECTION-FLICKER.md:11-22`, `notes/ABLETON-WINE-M4L-SELECTION-FLICKER.md:41-58`). +- An open M4L editor window adds audio latency; Ableton recommends closing editors (https://help.ableton.com/hc/en-us/articles/209072289-How-to-reduce-latency). +- Standalone Max 9 also runs under this Wine. The project ships a `max9` desktop entry and registers the `c74max:` URL scheme and the `.amxd` MIME type (`desktop/max9.desktop.in:1-11`, `desktop/wine-protocol-c74max.desktop.in:1-8`). +- Threading trivia with operational impact: Live names 46 threads `MainThread`; identifying threads by name misled an earlier investigation (`notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:225-228`). + +Implication for Wine: + +- Max inherits every Wine graphics and font defect Live hits, plus its own. The font deadlock shows a class of bug where Wine's honesty (reporting font failure that Windows hides) turns a Windows-latent defect into a Live hang. Similar Windows-lax behaviours elsewhere (font substitution, GDI mapper, EnumFontFamilies output) are worth auditing before chasing M4L reports (`notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:182-197`). +- The `WaitForVBlank` semi-stub means M4L devices with continuous redraw (meters, jsui) are paced by `Sleep(16)`, not by real vblank. Unverified: whether this costs UI smoothness or CPU in normal use; it is the known pacing point for all Max rendering (`notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:86-88`). +- Known upstream failure mode to keep in mind: Live freezing at "Starting Max..." is reported on other Wine builds (https://github.com/Frogging-Family/wine-tkg-git/issues/1226); this fork's font fix addresses one specific trigger, not the general class. + +## Link + +How Ableton Link works here: + +- Link is Ableton's protocol for synchronizing beat, tempo, phase, and start/stop across applications on one or more devices (https://ableton.github.io/link/, SDK at https://github.com/Ableton/link). +- Wire protocol: peer discovery over UDP multicast on `224.76.78.75:20808`; pairwise timeline measurement over unicast UDP on ephemeral ports (`notes/ABLETON-WINE-LINK.md:170-173`). +- Live joins as its own peer inside the Wine process. Wine 11.11 already passes the socket options the SDK needs (`IP_ADD_MEMBERSHIP`, `IP_MULTICAST_IF`, `SO_REUSEADDR`); `WSAJoinLeaf` is a Wine stub but the SDK does not use it (`notes/ABLETON-WINE-LINK-FIRSTCLASS.md:9-16`). No patch in this project touches networking. +- The project ships `ableton-linkd`, a native peer built on the vendored Link 4.0 SDK, which holds session tempo and timeline across Live restarts and enables Start Stop Sync (`notes/ABLETON-WINE-LINK.md:52-64`). A systemd user unit keeps it running (`scripts/ableton-linkd.service`). +- Live's own Link heartbeat shows up as a periodic `Log.txt` write, which once masqueraded as a settings-change signal (`notes/FINDINGS-LIVE-THEME-PREVIEW-SIGNAL-2026-07-26.md:31-33`). +- Live 12.4 (released May 2026) added Link Audio: real-time multichannel audio streaming between Link-enabled devices over LAN, with Live and Push 3 sending and receiving (https://www.production-expert.com/production-expert-1/ableton-live-124-released-link-audio-updated-devices-and-move-and-note-20, https://help.ableton.com/hc/en-us/articles/25425913328924-Link-Audio-FAQ). This project's implementation explicitly leaves Link Audio out (`notes/ABLETON-WINE-LINK-FIRSTCLASS.md:161`). + +Implication for Wine: + +- Classic Link works through stock Wine sockets; the stability surface is the host firewall, multicast-unfriendly access points, and prefix state, not Wine code (`notes/ABLETON-WINE-LINK.md:162-166`). +- Link Audio raises the stakes: it is timed audio over the LAN arriving as an input in Live, so it joins the audio clock domain. Unverified: how Link Audio performs under this stack; it needs the same xrun-style measurement as PipeASIO before anyone claims support. +- PipeASIO has no JACK transport layer, so external JACK bridges (`jack_link`) cannot sync Live; Live must follow the shared timeline itself (`notes/ABLETON-WINE-LINK.md:180-183`). + +## File handling + +How Live touches files: + +- Live Sets (`.als`) are gzipped XML; the saving Live version is in the `Creator` attribute (`notes/FINDINGS-TEMPO-RAMP-2026-07-31.md:75-76`). +- File dialogs: patch 0031 routes 64-bit `GetOpenFileName`, `GetSaveFileName`, `IFileDialog`, and `SHBrowseForFolder` through the XDG Desktop Portal; 32-bit callers and unsupported options fall back to Wine's chooser (`notes/ABLETON-WINE-FILE-PORTAL.md:9-21`). +- "Show in Explorer": Live shells out with `explorer.exe /select,""` for files and `explorer.exe /e,""` for the library panel. Patches 0043, 0063, and 0064 route these to the host file manager through the portal and `org.freedesktop.FileManager1` (`notes/ABLETON-WINE-SHOW-IN-EXPLORER.md:13-27`, `notes/ABLETON-WINE-SHOW-IN-EXPLORER.md:78-99`). +- Live's browser: Wine reported Unix mount boundaries as reparse points without data, and Live omitted them; patch 0033 (`WINE_DISABLE_UNIX_MOUNT_REPARSE`, set by the launcher) reports them as directories (`notes/ABLETON-WINE-ENCORE-REVIEW.md:11-20`). +- Patch 0060 implements `IFileOperation::DeleteItem` in shell32 (`patches/0060-shell32-implement-IFileOperation-DeleteItem.patch`). +- Live 11 loads `wmvcore.dll` (Windows Media Format) for browser preview and import of WMA/video files; Wine's stubs raise `EXCEPTION_WINE_STUB` and crash it (`notes/ABLETON-WINE-LIVE11-WMVCORE-STUB.md:10-18`). +- Disk load is a real audio-path factor: the disk overload indicator flashes when audio cannot be read or written fast enough, causing gaps on record and dropouts on playback (https://www.ableton.com/en/manual/computer-audio-resources-and-strategies/). + +Implication for Wine: + +- File handling under Live is mostly correctness work, now largely done. The remaining performance-relevant path is sample streaming from disk into the audio engine, which crosses Wine's file I/O (`ntdll`/`kernel32`) on threads that feed the audio deadline. Unverified: whether Wine file I/O adds measurable latency to Live's disk streaming versus Windows; no note in this repo measures it. +- Every new shell32/comdlg32 routing patch needs a 32-bit-caller story, because new WoW64 cannot load the 64-bit portal Unix library (`notes/ABLETON-WINE-SHOW-IN-EXPLORER.md:111-114`). + +## Authorization and online services + +How licensing and online features work: + +- Authorization binds to the prefix's `MachineGuid`. A response produced for another prefix cannot authorize this installation (`notes/ABLETON-WINE-ONLINE-AUTH.md:19-20`). +- Online flow: Live opens an Ableton HTTPS URL, Wine passes it through `winebrowser` and `xdg-open` to the host browser, Ableton returns an `ableton:` URL or a downloadable `.auz` file, and the desktop MIME system routes both back through the launcher into the prefix (`notes/ABLETON-WINE-ONLINE-AUTH.md:8-17`). This matches Ableton's documented online and offline authorization flows (https://help.ableton.com/hc/en-us/articles/209773585-Authorizing-Live-Online, https://help.ableton.com/hc/en-us/articles/360000573444-Authorizing-Live-Offline). +- The return path is contested: another Wine prefix can overwrite the `wine-protocol-ableton.desktop` handler. The installer replaces wrong handlers, pins defaults with `xdg-mime`, and the launcher repairs them (`notes/ABLETON-WINE-ONLINE-AUTH.md:25-60`). +- Live's in-app network access can fail under Wine; the offline `.auz` path is the fallback (`notes/ABLETON-WINE-LIVE11-WMVCORE-STUB.md:102-103`). +- Online-adjacent components that update themselves inside the prefix: Live's auto-updater and the WebView2 Evergreen runtime, which updated to 149 inside the prefix on 2026-07-24 without any installer involvement (`notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md:33-36`). +- Live 12.3's Splice integration streams commercial sample content into a WebView2 pane (https://www.ableton.com/en/blog/live-12-3-is-here/). +- Push 2's display helper (`Push2DisplayProcess.exe`) is a separate Ableton process that speaks libusb; patch 0032 bridges its 16-function Win64 libusb ABI to host libusb so the display works while ALSA keeps the MIDI interfaces (`notes/ABLETON-WINE-PUSH2-DISPLAY.md:24-52`). + +Implication for Wine: + +- Authorization stability is launcher and MIME plumbing, not Wine internals; the failure mode is silent misrouting after another prefix installs a handler (`notes/ABLETON-WINE-ONLINE-AUTH.md:28-40`). +- Self-updating components inside the prefix (WebView2, Live itself) are uncontrolled variables in any performance experiment. Record versions before benchmarking, as the WebView2 guardrail does (`notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md:186-189`). +- The Push 2 bridge is a fixed 16-function ABI. Live updates could extend the helper's libusb usage beyond it; the bridge's limits are documented (`notes/ABLETON-WINE-PUSH2-DISPLAY.md:135-147`). + +## Observed crash and hang classes + +Classes observed on this stack, with root cause and status. Signatures make them recognizable in user reports. + +| Class | Signature | Root cause | Status | Evidence | +|---|---|---|---|---| +| Audio enumeration hang/crash | Log stops after `Audio In Out: Constructor finished` | Stopped WirePlumber or endpoint-registry name rewrapping | Fixed (patch 0021) plus runbook | `notes/ABLETON-WINE-AUDIO-CRASH-BUG.md:3-36` | +| OpenGL plugin editor crash | `BadMatch` X error, window to 1x1 on first paint | Depth-24 pict format on depth-32 ARGB window | Fixed (patch 0026) | `notes/ABLETON-WINE-GL-PLUGIN-EDITOR-CRASH-BUG.md:5-35` | +| WebView2 plugin close crash | Serious-program-error dialog closing Splice editor | Cross-process `RevokeDragDrop` on helper-owned window | Fixed (patch 0045) | `notes/ABLETON-WINE-WEBVIEW2-PLUGIN-CLOSE-CRASH.md:9-24` | +| M4L device load hang | Window black, audio playing, 100% reproducible | MaxPlug font fallback chain dead-ends into deadlock | Fixed by vendored fonts; Max defect remains | `notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:3-41` | +| Idle CPU burn | APC coalescing thread at 30-40% of a core | High-frequency alertable waits through wineserver (hypothesis) | Open; `-DontCombineAPCs` is not a fix | `notes/ABLETON-WINE-APC-COALESCING.md:2-28` | +| Synchronization throughput collapse | wineserver at 45% core, 9k ctx/s | Build omitted ntsync | Fixed (vendored UAPI header, build gate) | `notes/ABLETON-WINE-NTSYNC-REGRESSION.md:3-14` | +| Window layout loop | 175-400 no-op `SetWindowPos`/s, 80-99% core | Mixed process/thread DPI states | Fixed (IFEO, patches 0040/0042) | `notes/ABLETON-WINE-RESIZE-BUG.md:9-63` | +| Menu/dropdown loss | Lost clicks, menus closing instantly | FocusOut cancels tracking; mapped popup flipped to managed | Fixed (patches 0038/0039) | `notes/ABLETON-WINE-MENU-FOCUSOUT.md:9-29`; `notes/ABLETON-WINE-DROPDOWN-MANAGED-FLIP.md:28-52` | +| Plugin window creation failure | `VST3: plug window creation failed`, 2.4 s stalls | Read-only shared session mapping stopped updating | Fixed (patches 0018/0019) | `notes/ABLETON-WINE-INPUT-BUG.md:38-53` | +| Plugin editor dead to input | Editor paints but ignores mouse | Orphaned dcomp subclass swallowed window procedure | Fixed (patch 0016) | `notes/ABLETON-WINE-INPUT-BUG.md:9-24` | +| Rust plugin abort | Process abort opening nih-plug editor | EGL backend advertised no sRGB pixel formats | Fixed (patch 0020) | `notes/ABLETON-WINE-INPUT-BUG.md:61-68` | +| WebView2 pane flicker | Grey cutout alternating at 5 Hz | Two writers (Wine reblit, Chromium software frame) on one window | Resolved by GPU renderer; upstream dcomp work pending | `notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md:38-52` | +| Direct-present black band | Frame drawn low, hit-testing correct | DPI-context disagreement on present path | Fixed (patches 0058/0059) | `notes/ABLETON-WINE-GPU-RENDERER.md:99-171` | +| M4L selection black flash | Whole window flashes black on track reselect | Full-client attach/detach of offscreen client surface | Fixed (patch 0062) | `notes/ABLETON-WINE-M4L-SELECTION-FLICKER.md:11-58` | +| Live 11 media crash | `EXCEPTION_WINE_STUB` 0x80000100 | `wmvcore.dll` stubs raise on call | Open; needs trace to identify export | `notes/ABLETON-WINE-LIVE11-WMVCORE-STUB.md:10-40` | +| Live 11 ASIO distortion | Distorted PipeASIO output; MME/DirectX fine | Unknown (issue 14) | Open | `notes/ABLETON-WINE-LIVE11-WMVCORE-STUB.md:93-97` | +| Tempo ramp timing drift | Export 0.08% shorter than Windows | Leading hypothesis: different Live versions | Open; per-buffer evaluation ruled too small | `notes/FINDINGS-TEMPO-RAMP-2026-07-31.md:110-131` | + +Reported upstream and elsewhere, for pattern matching: + +- wineserver using a full CPU core with Live 10, fixed by a patch (Wine bug 47281, https://bugs.winehq.org/show_bug.cgi?id=47281 — page content not re-verified; Bugzilla currently sits behind an anti-bot wall, summary per search index). +- Live 12 severe graphical issues on default options; `-_ForceGdiBackend` workaround left Max devices' UIs frozen (Wine bug 57260, https://list.winehq.org/archives/list/wine-bugs@list.winehq.org/thread/DUN3WQJ4TUSHKDA37BVL3PELHXZD6BRP/). +- Live 12 crashes opening sets saved in older versions, and M4L freezes (Wine bugs 56540 and 56537, https://list.winehq.org/hyperkitty/list/wine-bugs@list.winehq.org/thread/O2SD7WTZRJJPOQRWUBMG6CGACLCE6FPQ/). +- Freeze at "Starting Max..." on wine-tkg (https://github.com/Frogging-Family/wine-tkg-git/issues/1226). +- The `-DontCombineAPCs` workaround predates this project in the Wine-NSPA community notes (https://github.com/nine7nine/Wine-NSPA/issues/4). +- Independent press coverage confirms the current stack runs Live 12 and Push on Linux (https://cdm.link/ableton-live-on-linux/). + +Implication for Wine: + +- Most historical crash classes are window-management and DPI defects, now patched. The open classes cluster around three areas: thread wakeup and synchronization cost (APC coalescing), vendor-component behaviour Wine cannot patch away (Max fonts, WebView2 Evergreen), and Live 11 media support (`wmvcore`). +- Every fixed class has a probe or audit check in `tools/`, `scripts/`, or the tester kit. New performance work should ship with the same kind of guard, because several of these bugs regressed silently once before (ntsync, WebView2 149). + +## Key opportunities + +1. **Same-process APC fast path through ntsync's alert event.** Impact: high (removes the 30-40% idle-core burn without the `-DontCombineAPCs` playback fault). Effort: high (must preserve FIFO ordering, special APCs, I/O completion ordering; needs a new `apcprobe`). Evidence: proposal and verification plan in `notes/ABLETON-WINE-APC-COALESCING.md:31-73`; ntsync throughput table in `notes/ABLETON-WINE-NTSYNC-REGRESSION.md:38-44`. +2. **Measure and narrow the launcher's blanket `SCHED_RR` policy.** Impact: medium (potential low-core win and priority-inversion removal; currently unmeasured). Effort: medium (A/B harness and bench script already exist). Evidence: untested hypotheses and the pending comparison protocol in `notes/ABLETON-WINE-RT-SCHEDULING.md:30-79`. +3. **Implement a real `dxgi_output_WaitForVBlank`.** Impact: medium (correct pacing for Max device redraw and any Live code waiting on vblank instead of `Sleep(16)`). Effort: medium. Evidence: semi-stub identified at `notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:86-88` (`dlls/dxgi/output.c:371`). +4. **Extend the direct GL present path to `WS_POPUP` and `WS_CHILD` windows.** Impact: medium (kills the remaining 650 MB/s-class copy traffic for Settings, auth dialog, and embedded plugin editors). Effort: medium (must solve the black-before-first-input popup issue). Evidence: `notes/ABLETON-WINE-GPU-RENDERER.md:64-86`. +5. **Close the PipeASIO validation gaps.** Impact: medium (sample-rate changes while open, single-rate hardware, controlled xrun comparisons). Effort: low. Evidence: gap list in `notes/ABLETON-WINE-PIPEASIO.md:87-96`. +6. **Audit Wine font APIs for Windows-lax behaviour Max relies on.** Impact: medium (prevents the next M4L hang class; current fix removes one trigger, not the flaw). Effort: medium. Evidence: `notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:14-19` and rejected-approach findings at `notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:182-197`. +7. **Support Link Audio (Live 12.4) or document its behaviour.** Impact: medium (new Live feature currently unowned; timed network audio joins the clock domain). Effort: high if implemented, low if measured and documented. Evidence: `notes/ABLETON-WINE-LINK-FIRSTCLASS.md:161`; feature description at https://help.ableton.com/hc/en-us/articles/25425913328924-Link-Audio-FAQ. +8. **Resolve the tempo-ramp export difference (issue 101).** Impact: low-to-medium (timing correctness; likely a Live-version mismatch, but unproven). Effort: low (export test needs no Windows machine). Evidence: pending tests in `notes/FINDINGS-TEMPO-RAMP-2026-07-31.md:132-152`. +9. **Identify and implement the `wmvcore` export Live 11 calls.** Impact: low (Live 11 is experimental). Effort: medium. Evidence: planned fix in `notes/ABLETON-WINE-LIVE11-WMVCORE-STUB.md:31-40`. +10. **Benchmark Wine file I/O for sample streaming against the audio deadline.** Impact: unknown, potentially medium (disk overloads cause dropouts on the real-time path). Effort: low-to-medium. Evidence: disk-overload behaviour at https://www.ableton.com/en/manual/computer-audio-resources-and-strategies/; no repo measurement exists yet (stated in the file-handling section above). diff --git a/notes/performance-moonshot/docs/ABLETON-WINE-PERFORMANCE-PLAN.md b/notes/performance-moonshot/docs/ABLETON-WINE-PERFORMANCE-PLAN.md new file mode 100644 index 00000000..025bd663 --- /dev/null +++ b/notes/performance-moonshot/docs/ABLETON-WINE-PERFORMANCE-PLAN.md @@ -0,0 +1,509 @@ +# Ableton Wine performance and stability plan + +Review date: 2026-08-01. Target: Ableton Live 12.4.3 on the Wine 11.13 base +recorded in [`patches/BASE.txt`](../patches/BASE.txt). + +This review separates measured behavior, source inspection, and proposed work. +The proposals are not performance claims. Each proposal needs a controlled test +before it becomes a default. + +## What should change first + +The first target is audio deadline reliability. Average CPU use is secondary. +Live must finish every audio buffer before the device consumes it. + +| Order | Change | Expected result | Main risk | +| --- | --- | --- | --- | +| 0 | Expand the benchmark and regression gates. | Comparable results and safe rejection of regressions. | Poor workloads can give false confidence. | +| 1 | Implement Windows multimedia thread scheduling and remove process-wide real-time scheduling. | The audio threads run early without raising the UI, scanner, indexer, and browser processes. | A bad priority order can stall the audio callback or the desktop. | +| 2 | Trace and shorten same-process wake-ups, starting with queued audio work. | Less wineserver work and lower wake-up delay. | Windows ordering rules are strict. | +| 3 | Report processor topology and allowed CPUs accurately. | Live assigns audio work to the intended cores and keeps its available parallelism. | Incorrect core classes can reduce capacity. | +| 4 | Replace recurring DirectComposition repair work with current composition handling. | Lower idle and interaction CPU use; remove polling helpers. | Child windows and WebView2 depend on the current workarounds. | +| 5 | Update and instrument PipeASIO. | Measured callback delay, clock drift, buffer state, and audio buffer failures. | An upstream update has not yet been confirmed with Live. | +| 6 | Test a native PipeWire Wine driver for Windows audio outside ASIO. | Lower delay and CPU use for Max, browser media, helpers, and fallback audio. | Live's main ASIO path will not use it. Published results are from one machine. | +| 7 | Test Vulkan renderers behind a switch. | Possible lower render overhead and fewer driver-specific OpenGL faults. | This fork's DirectComposition work depends on Wine DXGI and wined3d behavior. | +| 8 | Port only measured server-bypass work from research forks. | Lower startup, file, message, or wait overhead where traces justify it. | Broad bypasses enlarge the correctness burden. | +| 9 | Test profile-guided build optimization last. | Smaller gains in Wine code that remains hot after structural work. | Most audio processing runs in Live and plug-in binaries, not Wine. | + +Do not merge an experiment unless it preserves audio output, MIDI order, file +behavior, window behavior, clean shutdown, and recovery from a forced exit. + +## What this project uses now + +This repository packages a focused Wine fork. It is not a copy of the full Wine +source tree. The build unpacks a pinned source archive, applies 62 Wine patches +and two PipeASIO patches, then packages the result. The process is defined by +[`Containerfile`](../Containerfile), +[`scripts/container-build.sh`](../scripts/container-build.sh), and +[`patches/BASE.txt`](../patches/BASE.txt). + +| Area | Current technology | Current behavior | +| --- | --- | --- | +| Wine base | `giang17/wine` `d2d1-dcomp-11.13` at `5c23dd1c` | Adds the Direct2D and DirectComposition support that Live and WebView2 need. | +| Build | Podman; Ubuntu 22.04 snapshot; GCC; Clang and lld 21.1.8; ccache | Builds Windows-format 64-bit and 32-bit modules with Wine's newer 64-bit layout for 32-bit support. Build inputs and hashes are pinned. | +| Synchronization | Wine kernel synchronization (`NTSync`) plus a vendored Linux header | Uses `/dev/ntsync` on a suitable kernel. Otherwise it falls back to wineserver. Both Wine halves are checked at build time. | +| Main graphics | Live Direct3D 11 (`D3D11`) and DirectX Graphics Infrastructure (`DXGI`); Wine wined3d; OpenGL and GLX; X11 or XWayland | The main Live window can present directly from the graphics card. This avoids the old full-window readback. | +| Embedded graphics | Wine Direct2D and DirectComposition; WebView2 forced to software rendering | Local patches repair composition, resizing, hidden panes, child windows, and fractional scaling. | +| Audio | Live Audio Stream Input/Output (`ASIO`); PipeASIO 1.2.2; native PipeWire | Opens a direct PipeWire node. The default is two inputs, two outputs, 48 kHz testing, and 256 frames. | +| MIDI | Wine ALSA MIDI | A local patch restores devices after reconnect. PipeASIO supplies the clock value used for ASIO MIDI timing. | +| Other Windows audio | Wine PulseAudio and ALSA drivers | Used outside the main PipeASIO path. | +| Media | Wine GStreamer | Imports MP3 and video through host GStreamer libraries. | +| Desktop integration | XDG portals and D-Bus | Provides file dialogs, file reveal, and folder opening. | +| Hardware | A narrow native `libusb` bridge | Supports the 64-bit Push 2 display helper. | +| Network sync | Ableton Link 4.0 native helper | Keeps a classic Link peer present across Live restarts. It is not a Link Audio test. | +| Support code | Bash, C, C++, and one Python audit tool | Provides launch, setup, probes, packaging, and the Link helper. | + +The shipped series has most of its source changes in `winex11`, `win32u`, +`comdlg32`, `dxgi`, and `wined3d`. It has little local work in `ntdll`, +`server`, `mmdevapi`, and `winealsa`. This is an engineering history, not a +runtime profile, but it shows that scheduling and wait paths have received less +local work than graphics and desktop integration. + +The project does not ship DXVK, vkd3d-proton, Wine Staging, a Proton runtime, +or a Steam container. + +## How Live divides its work + +### The audio deadline is the controlling measure + +Live processes independent signal paths in parallel. Devices on the same path, +sidechains, routed tracks, and mix points must run in order. The longest ordered +path limits the set. Live supports up to 64 audio calculation threads, but more +threads cannot shorten one ordered path. Its CPU meter reports buffer processing +time against buffer playback time; it does not report total machine CPU use. +[Ableton documents this scheduling model and meter behavior](https://help.ableton.com/hc/en-us/articles/209067649-Multi-core-performance-in-Ableton-Live-FAQ). + +The active audio path is: + +```text +Live audio workers -> ASIO callback -> PipeASIO -> PipeWire -> audio device +``` + +At 48 kHz, a 256-frame buffer lasts 5.33 ms. A 64-frame buffer lasts 1.33 ms. +The useful measure is the worst callback and worker delay relative to that +deadline. A low average can still produce an audio buffer underrun or overrun +(`xrun`). + +### Live asks Windows to schedule multimedia work + +Local inspection of the installed 64-bit Live 12.4.3 files found imports for: + +- the Windows multimedia scheduling calls `AvSetMmThreadCharacteristicsW`, + `AvSetMmThreadPriority`, and `AvRevertMmThreadCharacteristics` in Live and its + audio engine; +- the Windows real-time work queue library (`RTWorkQ.dll`) in the audio engine; +- Windows thread priority, affinity, processor topology, waitable timers, and + high-resolution clocks; and +- the `Pro Audio` multimedia task name. + +Windows uses these APIs to raise only time-sensitive multimedia threads while +retaining CPU time for other work. The [Windows low-latency audio example](https://learn.microsoft.com/en-us/windows/win32/coreaudio/exclusive-mode-streams) +uses the same `Pro Audio` task for a buffer thread. + +The pinned Wine base does not provide that behavior: + +- [`avrt.dll` returns placeholder success](https://github.com/giang17/wine/blob/5c23dd1c/dlls/avrt/main.c#L57-L92); +- the multimedia registration functions in + [`RTWorkQ.dll` return `E_NOTIMPL`](https://github.com/giang17/wine/blob/5c23dd1c/dlls/rtworkq/queue.c#L1663-L1724); and +- the Linux Wine server maps ordinary Windows priorities to `nice`, then stops + below the Windows real-time band. Its source contains an explicit + [`SCHED_RR` follow-up](https://github.com/giang17/wine/blob/5c23dd1c/server/thread.c#L236-L269). + +The launcher currently compensates by starting the entire Wine process at +`SCHED_RR` priority 10 when permitted. Every inheriting UI, browser, scanner, +and background thread receives that policy. PipeASIO separately requests +`SCHED_FIFO` priority 15 for its data loop. Wineserver remains under normal +scheduling. The risks are recorded in +[`notes/ABLETON-WINE-RT-SCHEDULING.md`](ABLETON-WINE-RT-SCHEDULING.md). + +### Live depends on accurate processor information + +On hybrid processors, Live normally puts audio work on performance cores. Its +Windows 11 default differs from its Windows 10 default. Live also imports the +Windows topology and affinity APIs. + +The pinned Wine base counts online host CPUs rather than CPUs allowed to the +process. It reports one processor group, has an explicit limit in the code for +systems above 64 logical processors, and obtains performance-core data from a +single optional Linux path. The launcher's proposed cap of eight CPUs is marked +as inactive groundwork. Enabling that cap would conflict with Live's ability to +use up to 64 audio threads. + +### Live is a set of processes and embedded platforms + +Local runtime and binary inspection found these main roles: + +| Role | Process or technology | Performance concern | +| --- | --- | --- | +| Main application | Live executable and `Ableton Live Engine.dll` | Audio deadlines, UI, plug-ins, project state, and recovery. | +| Plug-in discovery | Ableton Plugin Scanner | Startup CPU, directory scans, process exit, and malformed plug-ins. | +| Content search | Ableton Index | Background file reads, database writes, and change notices. | +| Network services | Web Connector and Link | Browser handoff, network changes, clocks, and shutdown. | +| Max for Live | Max, MaxRT, CEF, Node, and helpers | A second application platform with its own audio, graphics, file, and process behavior. | +| Web content | Edge WebView2 browser and renderer processes | DirectComposition, child windows, drag and drop, resizing, and teardown. | +| Push | Qt/QML display process, MIDI, and USB helper | GPU presentation, USB transfer, device reconnect, and process lifetime. | +| Machine learning | ONNX Runtime and bundled models | CPU, memory, and file load during stem separation. The exact internal split is not public. | + +Ableton states that a loaded VST runs inside Live and can terminate Live when it +crashes. The scanner's separate process protects discovery, not playback. +[Ableton's crash guide describes this boundary](https://help.ableton.com/hc/en-us/articles/5301568366354-Reading-Ableton-Live-Crash-Reports). + +### Live 12.4 adds a second real-time network path + +Live 12.4 added Link Audio. It streams audio between peers, buffers each peer, +and ties latency to both the network and the audio buffer. Live 12.4.3 fixed a +crash involving some Link Audio peers. These behaviors require a separate test +lane from classic Link tempo discovery. +[The Live 12 release notes describe Link Audio and its buffering](https://www.ableton.com/en/release-notes/live-12/). + +## What Proton changes for performance + +Proton's gains come from replacing expensive subsystems and controlling their +deployment. They do not come from one compiler setting. + +| Proton work | Effect | State in this project | Action here | +| --- | --- | --- | --- | +| In-process Windows synchronization | Proton 11 prefers NTSync, uses its earlier fast synchronization path (`fsync`) when NTSync is unavailable and the kernel supports it, then uses wineserver. This removes many server round trips. | NTSync is already compiled and checked. It is active only when `/dev/ntsync` is available. | Keep NTSync. Add a launch-time active-state report and reject silent fallback in performance tests. Consider fsync only as an opt-in fallback for older kernels. Never combine the two. | +| DXVK | Converts Direct3D 9, 10, and 11 to Vulkan. Modern versions prepare more graphics work away from the draw call to reduce some stalls. | Live uses Wine D3D11, DXGI, wined3d, and custom DirectComposition changes. | Test it in an isolated prefix. Do not make it the default until composition, child windows, plug-ins, WebView2, and audio-under-render-load pass. | +| vkd3d-proton | Converts Direct3D 12 to Vulkan. | The main Live renderer is D3D11. Some bundled machine-learning components may use other graphics APIs, but their division of work is not public. | Use only for a traced D3D12 feature. It is not a main-renderer optimization. | +| Steam Linux Runtime | Gives Proton a controlled user-space library set. | The build is controlled, but the installed runtime uses host graphics, PipeWire, GStreamer, portals, USB, and desktop libraries. | Keep the reproducible build. Test selective library bundling before a full runtime container, which would complicate low-latency devices and portals. | +| Per-application switches | Lets Valve disable one feature or select a fallback for one title without changing every title. | This project has several environment switches, but no unified experiment record. | Give every risky optimization one switch, one recorded default, and one benchmark pair. | +| Continuous rebasing and upstreaming | Removes local patches as Wine absorbs them and reduces long-term conflicts. | The fork carries 62 Wine patches and some experiment/revert pairs. | Rebase on a fixed cadence, drop patches that have no final effect, and upstream general fixes. | +| Focused component rebuilds and tests | Lets Proton change one component and check title regressions quickly. | The project has many probes and a beta plan, but the benchmark is narrow and Wine is configured with tests disabled. | Add a test build that runs the touched Wine module tests and the Live workload matrix. | +| Prefix control | Uses locks, versioned upgrades, and native case-insensitive directories where supported. | The launcher serializes initial setup, but prefix migrations and filename behavior have separate paths. | Keep every migration versioned and reversible. Test a new case-insensitive prefix for large plug-in trees; never alter an existing prefix in place. | +| Diagnostics | Supports per-application logs, early debugger attachment, crash directories, symbols, and unstripped builds. | This project has probes and privacy-aware reports, but no single performance crash bundle. | Record the Wine revision, kernel, active wait and audio paths, buffer, thread policies, underruns, and loaded module names. Redact user paths and license data. | + +Proton exposes NTSync, fsync, and renderer choices as reversible runtime options. +Its current README marks esync as obsolete in Proton 11. The Linux kernel +documentation states that NTSync exists because a user-space implementation +cannot provide both Windows semantics and comparable performance. Sources: +[Proton README](https://github.com/ValveSoftware/Proton), +[NTSync kernel documentation](https://docs.kernel.org/userspace-api/ntsync.html), +and [DXVK README](https://github.com/ValveSoftware/dxvk). + +Proton's own build remains conservative. It uses moderate optimization (`-O2`), +defined integer-overflow behavior, and information needed for useful crash +traces. Its distributed build targets broadly compatible x86 processors. It +does not use a general replacement allocator, whole-project link-time +optimization, profile-guided optimization, `-O3`, or `-march=native`. This +supports putting compiler experiments after wait, scheduling, audio, and +graphics work. + +## What other Wine projects provide + +The survey covers active or directly relevant projects. It is not a claim that +every public Wine branch is maintained or suitable for Live. + +| Project | Relevant work | Use here | Limit | +| --- | --- | --- | --- | +| [GE-Proton](https://github.com/GloriousEggroll/proton-ge-custom) | Valve Proton plus Wine Staging, current codecs, and application-specific fixes. | Use its patch history as a source of specific compatibility fixes. | Its defaults target Steam games. Importing the whole build would add unrelated behavior. | +| [wine-tkg and proton-tkg](https://github.com/Frogging-Family/wine-tkg-git) | Reproducible switches for Wine Staging, NTSync, older sync methods, and community experiments. | Use the configuration as an experiment index and compare one change at a time. | Many combinations are unsupported. Several older options are obsolete on modern Wine. | +| [Wine Staging](https://github.com/wine-staging/wine-staging) | Experimental fixes before or outside upstream Wine. Historical work includes Wine priority controls. | Review current patch groups by touched subsystem and test selected changes. | The old global real-time controls can slow applications and do not reproduce Windows multimedia scheduling. | +| [Wine-NSPA](https://nine7nine.github.io/Wine-NSPA/) | Per-thread priority mapping, priority-aware locks, same-process events and messages, server bypasses, direct audio callbacks, and extensive hot-path experiments. | Treat it as a research source. Start with scheduling, traces, and narrow same-process paths. | It changes Wine and the kernel together. Published performance results are self-reported and need local reproduction. | +| [Proton-CachyOS](https://github.com/CachyOS/proton-cachyos) and [`winepipewire.drv`](https://github.com/M0n7y5/wine-cachyos/tree/cachyos_11.0_release/_upstream_pipewire/dlls/winepipewire.drv) | A native PipeWire Wine audio driver and a separate [comparison harness](https://github.com/M0n7y5/winepipewire-bench). | Port the driver and unchanged harness behind build and runtime switches. Add Live, Max, capture, device-change, and long-run tests. | The published results are developer-owned, single-machine Windows Audio Session API (`WASAPI`) results. They do not measure Live through ASIO. | +| [wine-osu patches](https://github.com/whrvt/wine-osu-patches) | Small multimedia scheduling, PulseAudio callback, buffer, clock, wait, and server experiments for a latency-sensitive application. | Use its scheduling patch as a starting example and evaluate audio patches one at a time. | The scheduling example does not restore priority. The full patch set is application-specific, high-risk, and lacks one repository-wide license. | +| [ENCORE](ABLETON-WINE-ENCORE-REVIEW.md) | Ableton-focused launcher and Wine fixes. | Continue comparing its small, relevant changes. This project already adapted mount, drag, and resize fixes. | The original repository is no longer public at the recorded URL. The local review preserves the relevant findings. | +| [`giang17/wine`](https://github.com/giang17/wine) | The Direct2D and DirectComposition base used here, with later composition work. | Rebase or port the newer composition model before adding more timer repairs. | A large composition update needs the full window, display-scaling, plug-in, and WebView2 matrix. | +| [PipeASIO](https://m0n7y5.github.io/pipeasio/) | A direct PipeWire ASIO driver. Current 1.2.3 adds monitoring, live configuration, and device-clock handling. | Review the 1.2.2 to 1.2.3 update and reuse its measurements. | Upstream states that 1.2.3 is not yet confirmed with Ableton Live. | +| [WineASIO](https://github.com/wineasio/wineasio) | Mature JACK-backed ASIO with flexible channels, buffer changes, and JACK transport. | Keep a controlled comparison on the same machine and device. | It adds a JACK layer and does not explain Wine engine scheduling. | +| [yabridge](https://github.com/robbert-vdh/yabridge) | Real-time setup, plug-in process grouping, and practical Wine plug-in tests. | Reuse its host checks and scheduling lessons. | It runs Windows plug-ins in Linux hosts, which is the reverse of Live's normal path. | +| Upstream Wine Wayland driver | Direct Wayland output without XWayland. | Maintain a separate long-term branch and measure window latency and stability. | Most local window patches target `winex11`. A direct switch would discard years of tested behavior. | +| [Kron4ek Wine Builds](https://github.com/Kron4ek/Wine-Builds) and [Bottles runners](https://github.com/bottlesdevs/wine) | Build and delivery matrices for vanilla, Staging, TkG, Proton, and Bottles variants. | Use them as packaging comparisons. | Their aggressive build flags have no published Ableton evidence and are not a distinct runtime design. | + +The strongest outside ideas are narrow scheduling, fewer same-process server +calls, priority-aware locks, direct PipeWire for non-ASIO audio, better audio +instrumentation, and fewer repeated graphics copies. Large fork merges are not +justified. Current Wine Staging does not supply the older esync or fsync patch +sets, so old Staging tuning guides are not a Wine 11 plan. + +## How to test each high-priority change + +### 0. Measure missed deadlines and long delays + +[`scripts/bench-run.sh`](../scripts/bench-run.sh) records average +`wined3d_cs` CPU, wineserver context switches, an operator-entered xrun count, +and Live's audio-engine load meter. It does not record callback delay, thread +policy, wake-up delay, server request types, start time, memory faults, frame +timing, or crashes. + +Add these measures before performance patches: + +| Area | Required measure | +| --- | --- | +| Audio | Callback duration; time between callbacks; 99th, 99.9th, and worst delay; xruns; reported input and output latency; clock drift. | +| Scheduling | Policy, priority, CPU, voluntary switches, forced switches, wake-up delay, and runtime for each important thread. | +| Wine server | Requests by type, total handling time by type, queue depth, and blocked caller time. | +| Graphics | CPU by render thread, bytes read back from the graphics card, present interval, long frames, and memory use. | +| Startup | Cold and warm launch; project load; plug-in scan; index completion; helper creation and exit. | +| Files | Directory scan rate, metadata calls, mapped-file faults, flush time, and database lock waits. | +| Stability | Clean exits, forced-exit recovery, hangs, crashes, device loss, and a long playback soak. | + +Use one fixed Live set, one fixed plug-in set, a stable power state, and repeated +before/after runs in alternating order. Keep the existing 48 kHz and 256-frame +reference. Add 64 and 128 frames for deadline pressure, then 512 for slower +systems. Use 32 frames only on hardware that passes 64 frames. + +The test matrix must include: + +- idle, ordinary playback, and the longest practical ordered audio path; +- project load and save while audio runs; +- plug-in scan and content indexing while audio runs; +- VST2, VST3, Max for Live, OpenGL, D3D11, and WebView2 windows; +- repeated window resize, pane changes, plug-in window open and close, and + fractional scaling; +- MIDI input and output, hotplug, Push 2, and an interface with more than two + channels; +- Link Audio peer join, leave, packet loss, clock drift, and different buffers; +- X11 and XWayland; AMD, Intel, and NVIDIA graphics; and +- a low-core system, a many-core system, and an Intel hybrid processor. + +Keep the broader release matrix in [`beta/TESTING.md`](../beta/TESTING.md). + +### 1. Schedule the audio threads instead of the whole process + +Implement `avrt.dll` task registration and the related `RTWorkQ.dll` +registration calls. First map the observed `Pro Audio` class and relative +priority through Wine's existing per-thread `nice` support. Restore the original +state on unregister and thread exit, including nested registrations. Keep an +environment switch that restores the current behavior. + +Then test a separate mapping from the Windows real-time band to Linux +`SCHED_RR` or `SCHED_FIFO`, limited to registered multimedia threads and only +when the user has permission. This second step must remain off by default until +the starvation tests pass. + +Define and test a priority order. The PipeWire data loop, Live's audio work, +supporting Wine work, and wineserver must not wait on a lower-priority holder. +Do not select fixed production numbers until traces show which threads block +which callers. Never raise wineserver above the audio callback by default. + +Once the mapping passes, remove the launcher's inherited `chrt -r 10` default. +Keep it only as a comparison mode. This is the closest direct match for what +Live requests on Windows and the highest-confidence structural opportunity. + +Pass gates: + +- fewer or equal xruns and lower worst callback delay at 64, 128, and 256 frames; +- normal scheduling for UI, scanner, indexer, browser, and maintenance threads; +- exact priority restoration after `AvRevertMmThreadCharacteristics`; +- no starvation during plug-in scan, project save, or window movement; and +- Wine AVRT, RTWorkQ, thread, process, and multimedia tests. + +### 2. Shorten queued audio wake-ups without changing their order + +The local test that disabled batching for Windows queued asynchronous procedure +calls (`-DontCombineAPCs`) removed a 30–40% idle thread but made playback slow +and broken. The recorded analysis concludes that uncombined queued work likely +increased traffic through the single-threaded wineserver and delayed PipeASIO. +The option was correctly removed. See +[`notes/ABLETON-WINE-APC-COALESCING.md`](ABLETON-WINE-APC-COALESCING.md). + +First trace queued calls, alertable waits, server requests, and wake-up targets +during idle and playback. If the calls are same-process and dominate the trace, +prototype a client path backed by the existing NTSync alert event. Keep server +handling for cross-process work, I/O completion, suspension, termination, and +any case whose order cannot be proved locally. + +Borrow the narrow design pattern, not the full Wine-NSPA dispatcher. Test queue +order, cancellation, nested alertable waits, process exit, and 24-hour playback. + +### 3. Report allowed CPUs and core classes accurately + +Replace the inactive eight-CPU proposal with one consistent topology model: + +1. Start with CPUs in `sched_getaffinity()`, not all online CPUs. +2. Read package, core, sibling, and capacity data for those CPUs. +3. Report processor groups correctly above 64 logical processors. +4. Give every topology and affinity API the same numbering and masks. +5. Distinguish performance and efficiency cores only when the host provides + reliable data. +6. Compare the result with the same hardware running Windows 10 and Windows 11. + +Test Live's default and +`-RestrictAudioCalculationToPerformanceCores=true/false`. Do not assume that +performance-core-only is faster for every set. It can reduce parallel capacity. + +### 4. Remove recurring graphics repair work + +The current graphics work already produced the largest measured local gain: +enabling Live's D3D11 renderer reduced idle CPU from about 59% to 1–2%, and +direct OpenGL presentation removed about 650 MB/s of full-window display +traffic. These results are recorded in +[`notes/ABLETON-WINE-GPU-RENDERER.md`](ABLETON-WINE-GPU-RENDERER.md). + +The remaining composition path still uses periodic re-blits and a resident +`learnheal.exe` process that scans windows each second and performs a delayed +one-pixel resize. Later work in the base fork implements more complete +composition surfaces and skips unchanged composition trees. + +Rebase that work in an isolated branch. The goal is to delete timer repairs, +not add another timer. Remove `learnheal.exe` only after Learn View, Splice, +Max, plug-in editors, hidden panes, and repeated resizes pass without it. + +### 5. Make PipeASIO observable and current + +The current integration is sound but its performance evidence is incomplete. +The 2026-07-17 test recorded two PipeWire errors on a loaded machine and about +8% on Live's audio-engine load meter, but it was not a controlled comparison. +See +[`notes/ABLETON-WINE-PIPEASIO.md`](ABLETON-WINE-PIPEASIO.md). + +Review PipeASIO 1.2.3 against the two local patches. Adopt or expose: + +- callback interval and callback duration; +- driver and device rate, buffer size, selected ports, and clock owner; +- xrun count and reason; +- ASIO sample position, host time, input latency, and output latency; +- configuration changes without restarting Live; and +- more than two channels. + +Compare PipeASIO 1.2.2, 1.2.3, and WineASIO on the same device, graph, project, +and buffer. Preserve the current direct PipeWire path unless another driver +wins both deadline and correctness tests. + +### 6. Test native PipeWire for Windows audio outside ASIO + +Proton-CachyOS added `winepipewire.drv`, a direct PipeWire backend for Wine's +Windows audio APIs. Its developer's one-machine comparison reports lower stream +open time, event timing variation, and total client-plus-daemon CPU than +`winepulse.drv`, with the same tested `mmdevapi` results. These are useful leads, +not local evidence. + +Port it behind `--with-pipewire` and a runtime audio-driver switch. Run its +[published harness](https://github.com/M0n7y5/winepipewire-bench) unchanged +before adding local probes. Test shared and exclusive playback, capture, rate +changes, suspend, device removal, Max, WebView media, and a long run. + +This path does not replace PipeASIO. Live configured for ASIO bypasses Wine's +normal Windows audio driver. Keep PipeASIO as the main path and compare the new +driver as a fallback and for helper processes. + +### 7. Test graphics alternatives without replacing the known path + +Use two separate experiments: + +1. Wine's built-in wined3d Vulkan renderer. This retains more of Wine's DXGI + structure and is the lower-integration-risk first test. +2. DXVK for D3D11. This has the stronger game record but can bypass code that + the DirectComposition patches expect. + +Measure idle, continuous mouse movement, large window resize, animated plug-in +editors, WebView2, fractional scaling, and audio at 64 frames. Retain the +current OpenGL path as the control and fallback. + +### 8. Move only traced work out of wineserver + +Wine-NSPA reports large synthetic or local gains from same-process events, +message rings, empty message-poll caching, shared thread and process state, +local file operations, and a custom kernel dispatcher. Those changes are too +broad to import together. + +Add per-request timing to this fork, then select one frequent operation. A safe +order is: + +1. read-only thread or process queries; +2. repeated empty message polls; +3. eligible local file metadata during indexing; +4. same-process events and waits; and +5. any custom dispatcher or kernel change only after the user-space options + have reached a measured limit. + +Every fast path needs a server fallback and upstream Wine tests. Do not require +a custom kernel for the default runtime. + +### 9. Optimize startup after the playback path is stable + +Profile the scanner, indexer, database, directory changes, file mapping, and +helper-process lifetime. Live 12.1 already separates plug-in scan data from the +main content database. Repeated scans usually indicate a correctness problem, +not a need for faster scanning. + +Useful candidates are duplicate change-notice removal, correct file identity, +fewer metadata server calls, and clean helper exit detection. Generic +asynchronous file work or the Linux `io_uring` file API is justified only if a +trace shows blocked Wine file calls. Keep indexing below audio work. + +Also test a newly created prefix on a file system with native case-insensitive +directory support. This can avoid repeated case-insensitive searches in large +plug-in trees. Treat it as a new-prefix experiment. Do not convert an existing +authorized prefix in place. + +### 10. Test compiler work only on remaining Wine hot paths + +The build uses default Wine optimization. PipeASIO alone adds `-O2 -DNDEBUG`. +Possible experiments are a separate AVX2-capable artifact, link-time +optimization, and profile-guided optimization from the fixed Live workload. + +Do not use `-march=native` for a distributed runtime. Do not change +floating-point contraction, rounding, denormal handling, or exception behavior +without reference-render tests. Live requires AVX2, but that does not prove that +every wider instruction set is available. Measure Wine-side CPU, not Live's +total audio-engine load, when judging these builds. + +## What should not become a default + +- Do not combine the older event-based synchronization path (`esync`), fsync, + or a spin count with NTSync. NTSync already addresses the same wait problem + with stronger Windows semantics. An fsync experiment is only a fallback for + a host without `/dev/ntsync`. +- Do not run every Wine thread under `SCHED_FIFO` or `SCHED_RR`. +- Do not raise wineserver above audio callbacks without a blocked-caller trace. +- Do not enable the eight-CPU cap. +- Do not make DXVK, the wined3d Vulkan renderer, or native Wayland the default + before the complete window and audio matrix passes. +- Do not merge GE-Proton, wine-tkg, Wine Staging, or Wine-NSPA as a patch bundle. +- Do not use broad memory locking. Live, Max, browser processes, samples, and + machine-learning models can reserve large amounts of memory. Lock only small, + proven audio buffers and prepare their pages before playback. +- Do not require a custom kernel. Use a supported NTSync kernel for the fast + path and retain a correct fallback. +- Do not use allocator replacements, huge pages, `-O3`, or native CPU tuning + without a measured Wine hot path and a stability result. +- Do not set 0.2–0.4 ms audio periods as a default. They reduce the time + available to recover from ordinary scheduling delay. +- Do not disable runtime checks for speed. A media path can depend on those + checks for correct fault handling. +- Do not treat a lower buffer size as a performance win if it adds missed + deadlines. + +## How the work should be staged + +| Stage | Deliverable | Exit condition | +| --- | --- | --- | +| A | Benchmark collector and fixed workloads | Repeated control runs have stable distributions and complete metadata. | +| B | `avrt.dll`, `RTWorkQ.dll` multimedia registration, and per-thread priority mapping | Beats normal scheduling and process-wide `SCHED_RR` without starvation. | +| C | CPU topology correction | Matches Windows observations and improves or preserves both long-path and many-track tests. | +| D | Queued-work trace and one narrow fast path | Reduces server work and worst callback delay with full ordering tests. | +| E | Current DirectComposition rebase | Removes repair timers or helpers and passes the graphics matrix. | +| F | PipeASIO update and driver comparison | Produces reliable timing data and passes rate, buffer, channel, hotplug, and soak tests. | +| G | Native PipeWire Wine driver | Beats Wine PulseAudio for non-ASIO uses and passes playback, capture, device, Max, and soak tests. | +| H | Vulkan renderer trials | One renderer wins a defined workload without composition regressions. | +| I | One traced startup or server bypass | Shows a repeatable gain and retains a simple fallback. | +| J | Build optimization | Improves a remaining Wine hot path without numerical or compatibility changes. | + +Release each stage separately. Keep one switch that restores the prior path. +Record failed experiments as clearly as successful ones. + +## Evidence limits + +- Live is proprietary. Its internal worker scheduler and process protocols are + not public. Local conclusions use documented behavior, imports, process + observation, and sanitized runtime evidence; they do not assume unseen code. +- The exact Live calls into the Windows multimedia scheduling libraries still + need a trace. +- The role of Microsoft's DirectML graphics computing library in current + Windows features is not public. Ableton states that Windows stem separation + uses the CPU. +- Link Audio transport ports were not documented in the reviewed Ableton pages. +- PipeASIO 1.2.3, Wine-NSPA results, and fork claims have not been reproduced in + this repository. +- Patch counts show maintenance focus, not runtime cost. + +Primary external references used in addition to the linked project sources: +[Ableton Live 12 manual](https://www.ableton.com/en/live-manual/12/), +[Ableton Live 12 release notes](https://www.ableton.com/en/release-notes/live-12/), +[Ableton multi-core FAQ](https://help.ableton.com/hc/en-us/articles/209067649-Multi-core-performance-in-Ableton-Live-FAQ), +[Ableton audio resource guide](https://www.ableton.com/en/live-manual/12/computer-audio-resources-and-strategies/), +[Microsoft multimedia scheduling API](https://learn.microsoft.com/en-us/windows/win32/api/avrt/nf-avrt-avsetmmthreadpriority), +[Proton](https://github.com/ValveSoftware/Proton), and +[Linux NTSync documentation](https://docs.kernel.org/userspace-api/ntsync.html). diff --git a/notes/performance-moonshot/docs/AUDIO-LATENCY-ECOSYSTEM.md b/notes/performance-moonshot/docs/AUDIO-LATENCY-ECOSYSTEM.md new file mode 100644 index 00000000..30127040 --- /dev/null +++ b/notes/performance-moonshot/docs/AUDIO-LATENCY-ECOSYSTEM.md @@ -0,0 +1,134 @@ +# Audio latency ecosystem survey for the performance moonshot + +This document helps the reader decide which low-latency audio optimizations to adopt by comparing what this repository already ships against the current Wine and Linux pro-audio ecosystem. It covers WineASIO and its forks, upstream Wine audio drivers, yabridge, REAPER-on-Wine tunings, PipeWire pro-audio settings, realtime kernels, rtkit, JACK versus PipeWire, and native Linux DAWs as latency reference points. + +Terms used below. ASIO (Audio Stream Input/Output) is Steinberg's Windows low-latency driver API; it is the only audio driver Live uses on this project. An xrun is a buffer under- or overrun: the audio thread missed its deadline and the glitch is audible. Quantum is PipeWire's name for the buffer size in frames per processing cycle. Round-trip latency (RTL) is the time for a signal to enter the interface, pass through the software, and leave again. `SCHED_FIFO` and `SCHED_RR` are Linux realtime scheduling policies; a thread under either preempts normal threads. JACK is the traditional pro-audio server and client API. PipeWire is the current default Linux audio server; it can serve JACK, PulseAudio, and native clients at once. WirePlumber is PipeWire's session manager; it decides which devices and links exist. yabridge runs Windows VST plugins inside Linux DAWs by bridging each plugin between a native shim and a Wine process. + +## What this repository already ships + +Live's audio path is: Live (Windows process under this Wine fork) → PipeASIO (an ASIO driver implemented as a native PipeWire client) → PipeWire graph → ALSA → hardware. JACK is not in Live's path since release 2026.07.17.2 (`notes/ABLETON-WINE-PIPEASIO.md:3-4`). + +| Mechanism | State | Evidence | +|---|---|---| +| ASIO driver | PipeASIO 1.2.2, built from `vendor/pipeasio-1.2.2.tar.gz`, plus two local patches | `notes/ABLETON-WINE-PIPEASIO.md:27-30`, `scripts/container-build.sh:133-138` | +| Sample-rate clamp | Patch 0001 keeps the PipeWire graph rate when Live requests an unsupported rate, instead of returning `ASE_NoClock`, which crashed Live at startup | `patches/pipeasio/0001-asio-keep-graph-sample-rate-instead-of-ASE_NoClock.patch:14-30` | +| MIDI timebase | Patch 0002 reports `timeGetTime` in `ASIOSystemTime` so Live's incoming MIDI events land in its expected window | `patches/pipeasio/0002-asio-report-timeGetTime-in-ASIO-systemTime.patch:17-21` | +| Buffer default | 2 inputs, 2 outputs, fixed 256-frame buffer, auto-connect; `PIPEASIO_PREFERRED_BUFFERSIZE` overrides per launch | `notes/ABLETON-WINE-PIPEASIO.md:56-59` | +| Graph quantum matching | PipeASIO sets the PipeWire graph quantum to the ASIO buffer size on PipeWire 1.6 or newer | `notes/ABLETON-WINE-PIPEASIO.md:20` | +| Realtime scheduling | Launcher probes `chrt -r 10 true` and starts Wine under `SCHED_RR` priority 10; `ABLETON_RT=off` disables this | `scripts/ableton-live:780-782`, `notes/ABLETON-WINE-RT-SCHEDULING.md:3-7` | +| Driver thread priority | PipeASIO requests `SCHED_FIFO` priority 15 for its data-loop thread, independently of the launcher policy | `notes/ABLETON-WINE-RT-SCHEDULING.md:5-7` | +| Reconnect handling | WirePlumber restores PipeASIO streams after device hotplug; the old `jacklinkd` helper is no longer started for Live | `notes/ABLETON-WINE-AUDIO-HOTPLUG.md:1-8` | +| Resampling | PipeWire resamples a device whose rate differs from the graph rate | `notes/ABLETON-WINE-AUDIO-HOTPLUG.md:34-35` | +| Diagnostics | `scripts/check-live-audio.sh` launches Live and fails on `FatalError`, a hung `Open: started`, or a missing sample-rate line | `scripts/check-live-audio.sh:26-60` | +| Benchmark harness | `scripts/bench-run.sh` records PipeWire xrun deltas and Live DSP load into `bench/results.csv` | `notes/ABLETON-WINE-RT-SCHEDULING.md:66-79` | + +Measured baseline from 2026-07-17: about 8% Live DSP load at 48 kHz and 256 frames, with PipeWire's error counter rising by 2 during a short run on a loaded machine. The note itself says this is not a controlled latency comparison (`notes/ABLETON-WINE-PIPEASIO.md:83-85`). + +Open hypotheses already recorded: realtime throttling (Linux caps realtime tasks at 950 ms per second), all launcher-inherited threads sharing one `SCHED_RR` priority, and Live's realtime threads outranking the `SCHED_OTHER` wineserver they make synchronous calls to (`notes/ABLETON-WINE-RT-SCHEDULING.md:31-41`). The 4-CPU `taskset` comparison that would answer these is written down but marked pending (`notes/ABLETON-WINE-RT-SCHEDULING.md:43-81`). + +## WineASIO and its forks + +WineASIO is the original ASIO-to-JACK driver for Wine. The upstream repository is `wineasio/wineasio`; release 1.3.0 (July 2025) loads `libjack.so.0` dynamically at runtime, removes the ASIO SDK header dependency, and keeps configuration in the registry under `HKEY_CURRENT_USER\Software\Wine\WineASIO` with environment-variable overrides (https://github.com/wineasio/wineasio/). Its default is a fixed buffer size controlled by JACK, 16 inputs and 16 outputs, and a preferred buffer size of 1024 (https://github.com/wineasio/wineasio/). + +This project replaced WineASIO with PipeASIO because WineASIO summed inputs to mono and showed high latency under load (issue #4), and because WineASIO can only see ports that PipeWire's JACK layer exposes (issue #5) (`notes/ABLETON-WINE-PIPEASIO.md:9-13`). A second, structural reason appears in the hotplug note: when a device disappears, PipeWire removes the JACK links to WineASIO and does not restore them on return, so this project had to run `jacklinkd` to recreate links (`notes/ABLETON-WINE-AUDIO-HOTPLUG.md:12-19`). WineASIO's regression risk is also visible upstream: version 1.2.0 (September 2023) exists only to "fix compatibility with Wine > 8" (https://github.com/wineasio/wineasio/). No WineASIO fork adds native PipeWire support; PipeASIO is the native-PipeWire line. Unverified: this survey did not re-audit every WineASIO fork on GitHub as of August 2026. + +Relevance to the moonshot: the JACK detour is already gone. The remaining WineASIO-era gap is evidence. The two comparisons the evaluation note still lists as missing — reproducing issue #4 under WineASIO on the same interface, and comparing WineASIO and PipeASIO xruns under identical load — are still open (`notes/ABLETON-WINE-PIPEASIO.md:87-95`). + +## Upstream Wine audio drivers: winepulse.drv and winealsa.drv + +Live uses ASIO, so these drivers matter only as a fallback path and as context for how much latency a non-ASIO path adds. + +`winepulse.drv` implements the Windows WASAPI/MMDevice API over PulseAudio's API (which PipeWire serves through `pipewire-pulse`). In upstream Wine's `dlls/winepulse.drv/pulse.c`, the stream buffer target length is three periods (`attr.tlength = period_bytes * 3`), and the default device period is probed from the server's minimum request size times ten (https://raw.githubusercontent.com/wine-mirror/wine/master/dlls/winepulse.drv/pulse.c, as fetched August 2026). Three periods of buffering plus the server's own buffering puts the winepulse path far above ASIO latency. The same source contains no environment-variable latency override. Separate crash-bug evidence in this repo: with WirePlumber stopped, PipeWire exposes only `auto_null` and winepulse can block while Live enumerates endpoints (`notes/ABLETON-WINE-AUDIO-CRASH-BUG.md:22-24`). + +`winealsa.drv` talks to ALSA directly and can beat winepulse on latency, but it bypasses PipeWire, so it grabs the device exclusively and cannot share it with other applications — exactly what a desktop DAW setup must avoid. Community references still recommend JACK/WineASIO-style paths over both drivers for Live-class workloads (https://askubuntu.com/questions/1292282/high-latency-and-poor-sound-quality-when-running-ableton-live-daw-using-wine). + +Relevance to the moonshot: none of these drivers should ever serve Live's engine. The one actionable item is keeping the mmdevapi enumeration path (used by winepulse) from stalling Live's startup, which patch 0021 already addresses (`notes/ABLETON-WINE-AUDIO-CRASH-BUG.md:33-36`). + +## yabridge: architecture and what it implies for plugins inside Live + +yabridge runs Windows VST2, VST3, and CLAP plugins in Linux DAWs. Each plugin loads in a `yabridge-host.exe` Wine process; the DAW loads a small native `.so` shim; the two exchange audio, MIDI, and parameters through shared memory and UNIX sockets (https://github.com/robbert-vdh/yabridge, https://bonnef.in/posts/linux-music-production/). Measured bridge overhead is under 1 ms, below the buffer latency (https://bonnef.in/posts/linux-music-production/). + +Two design properties matter for this project: + +1. Process isolation. A crashing plugin kills its own Wine process, not the host's audio engine. yabridge's plugin groups deliberately trade this isolation away: grouping plugins into one process cuts loading time and lets instances share data, but a crash then takes the whole group (https://github.com/robbert-vdh/yabridge). +2. Call coalescing. yabridge 3.2.0 prefetches transport info and process level with each VST2 audio block, caching them on the Wine side to avoid back-and-forth IPC inside one processing cycle; this measurably cut overhead for chatty plugins (https://github.com/robbert-vdh/yabridge/blob/master/CHANGELOG.md). + +The implication for Live is structural and unfavorable. Live loads every VST in its own process space, inside the same process as the audio engine. A plugin that blocks, crashes, or misbehaves on the audio thread takes the whole engine down, and no PipeWire- or Wine-level tuning can change that. The yabridge model cannot be applied inside Live without re-implementing plugin hosting. What can be applied today is the workflow this repo already documents as untested: host heavy or unstable Linux-native plugins outside Live in Carla and route audio through PipeWire (`notes/ABLETON-WINE-PLUGIN-BRIDGING.md:1-6`). Unverified: whether Live 12's VST3 hosting isolates any plugin work off the realtime thread; this survey found no public evidence either way. + +yabridge's performance-tuning list doubles as the de-facto REAPER-on-Wine tuning list, because that is the community that produced it: realtime scheduling privileges, a kernel with full preemption (`preempt=full` or better), `threadirqs` plus rtirq to raise sound-card interrupt priority, the `performance` CPU frequency governor, and a Wine build with fsync (`WINEFSYNC=1`) for multithreaded plugins (https://github.com/robbert-vdh/yabridge). It also warns that rtkit-imposed `RLIMIT_RTTIME` of 200000 µs can silently cap realtime threads, and that GNOME 45 and newer force applications through rtkit (https://github.com/robbert-vdh/yabridge). + +## PipeWire pro-audio settings + +PipeWire's latency is `quantum / rate`. The upstream default quantum is 1024; at 48 kHz that is 21.3 ms per buffer, while 256 is 5.3 ms, 128 is 2.7 ms, and 64 is 1.3 ms (https://oneuptime.com/blog/post/2026-03-02-configure-pipewire-low-latency-audio-ubuntu/view). + +| Setting | Effect | Source | +|---|---|---| +| `default.clock.quantum` | Graph buffer size; default 1024 | https://oneuptime.com/blog/post/2026-03-02-configure-pipewire-low-latency-audio-ubuntu/view | +| `default.clock.min-quantum` / `max-quantum` | Bounds what clients may request | same | +| `default.clock.rate` / `allowed-rates` | Graph rate; restricting rates avoids runtime rate switches | same | +| `pw-metadata -n settings 0 clock.force-quantum N` | Forces the graph quantum at runtime | same; this repo's own check script already points users at `pw-metadata -n settings` for forced clock rates (`scripts/check-live-audio.sh:57-60`) | +| `module.rt` `rt.prio` | Realtime priority of PipeWire's data threads (example: 88) | https://oneuptime.com/blog/post/2026-03-02-configure-pipewire-low-latency-audio-ubuntu/view | +| `api.alsa.disable-batch`, `api.alsa.headroom`, `api.alsa.period-size`, `api.alsa.period-num` | ALSA device buffering in WirePlumber rules | same | +| `PIPEWIRE_LATENCY=256/48000` | Per-client latency request (frames/rate) | https://juij.fun/static/Wine%20%26%20Proton%20%E5%85%BC%E5%AE%B9%E5%B1%82%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E5%8F%8A%E5%90%AF%E5%8A%A8%E9%A1%B9%E5%8F%82%E6%95%B0%E5%8F%82%E8%80%83 | + +Two caveats from practice. USB interfaces often need 512 frames as the stable minimum, while built-in HDA can reach 256 and good PCIe cards 64 (https://oneuptime.com/blog/post/2026-03-02-configure-pipewire-low-latency-audio-ubuntu/view). And a 2023 community test found PipeWire stable at 256 samples (about 18 ms round trip) but producing xruns when pushed lower — dated, but a reminder that graph-wide stability, not the driver, is the usual binding constraint (https://linuxcreative.com/articles/pipewire-the-next-big-thing-in-linux-audio-production/). + +Interaction with this repo: PipeASIO already forces the graph quantum to the ASIO buffer on PipeWire 1.6+ (`notes/ABLETON-WINE-PIPEASIO.md:20`), but the shipped runtime only requires host PipeWire 0.3.56 (`notes/ABLETON-WINE-PIPEASIO.md:46-48`), so hosts on older PipeWire get no quantum matching and the graph runs at its default 1024 unless the user sets it. Unverified: the fallback behavior on those hosts has not been measured by this project. + +## Realtime kernels: PREEMPT, PREEMPT_DYNAMIC, PREEMPT_RT + +The PREEMPT_RT patchset merged into mainline Linux 6.12 (November 2024) after about twenty years out of tree, so current kernels build realtime support without external patches; ARM followed in Linux 7.1 (https://oneuptime.com/blog/post/2026-03-02-how-to-install-real-time-kernel-preempt-rt-on-ubuntu/view, https://www.phoronix.com/linux/Arm). Ubuntu ships a supported realtime kernel as `ubuntu-realtime`, and the lower-effort `linux-lowlatency` flavor covers most audio use (https://oneuptime.com/blog/post/2026-03-02-configure-pipewire-low-latency-audio-ubuntu/view, https://oneuptime.com/blog/post/2026-03-02-how-to-install-real-time-kernel-preempt-rt-on-ubuntu/view). + +The Arch pro-audio guide's current position: the vanilla kernel with `CONFIG_PREEMPT` is adequate for low latency in most cases; reach for a realtime kernel only when drop-outs persist or ultra-low latency is the goal, and enable `preempt=full` when the kernel is built as `PREEMPT_DYNAMIC` without full preemption selected (https://wiki.archlinux.org/title/Professional_audio). This matches yabridge's guidance (https://github.com/robbert-vdh/yabridge). + +Known 2026 wrinkle: scheduler work in late 2025 added `PREEMPT_LAZY` partly because PREEMPT_RT suffered over-scheduling that hurt throughput relative to non-RT kernels (https://patchew.org/linux/20251219101502.GB1132199@noisy.programming.kicks-ass.net/). For a DAW workload the trade still favors RT or at least full preemption, but "RT kernel" is not a free win; it mainly buys worst-case scheduling jitter, not average speed. + +## rtkit and realtime privileges + +rtkit (RealtimeKit) is a D-Bus service that grants realtime scheduling to unprivileged processes under strict caps. Its daemon hands out at most realtime priority 20 and only to processes whose `RLIMIT_RTTIME` is at most 200 ms; it never accepts an unlimited RTTIME (https://sources.debian.org/src/rtkit/0.10-2+wheezy1/rtkit-daemon.c/). RTTIME is the kernel's per-process realtime CPU-time budget; exceeding it kills or throttles the thread. + +This creates two failure modes relevant here. First, a user without `rtprio` limits falls back to rtkit, whose priority ceiling of 20 sits below the priorities JACK and PipeWire documentation assume; the pro-audio consensus is to prefer `RLIMIT_RTPRIO` through `limits.conf` (or the `realtime-privileges` package) over rtkit (https://github.com/rerdavies/pipedal/discussions/99). Second, yabridge documents the 200 ms RTTIME cap as a concrete cause of warnings and throttled audio threads, and notes GNOME 45+ forces this path (https://github.com/robbert-vdh/yabridge). + +This repo's launcher needs `rtprio` to succeed at its `chrt -r 10` probe (`scripts/ableton-live:780-782`), and `scripts/setup-realtime.sh` installs that permission; the script deliberately leaves out a wineserver `chrt -f -p 95` boost because it needs root (`scripts/setup-realtime.sh:13-23`). The moonshot decision to make is whether the priority ladder — PipeWire data threads (rtkit/rt.prio), Live under RR 10, PipeASIO at FIFO 15, wineserver at `SCHED_OTHER` — is the right order. Today the component Live's realtime threads block on most, wineserver, has the lowest priority. That is a classic priority-inversion shape, already listed as an unconfirmed hypothesis in the RT note (`notes/ABLETON-WINE-RT-SCHEDULING.md:38-41`). + +## JACK versus PipeWire for this use case + +The Arch guide calls JACK the mature, pro-audio-designed server and PipeWire "a sufficient server for most of the use cases," while noting open doubt about PipeWire for professional work (https://wiki.archlinux.org/title/Professional_audio). The strongest 2026 measurement found is the SuperCollider scsynth experiment: a native PipeWire backend showed 40-50% lower CPU than the JACK-on-PipeWire shim at equal DSP load, but pinning both to the same cores erased the gap — the kernel had been placing the shim's audio thread on an efficiency core and the native client's thread on a performance core of a hybrid CPU (https://scsynth.org/t/experimental-native-pipewire-audio-backend-for-scsynth-linux-feedback-wanted/13292). Two conclusions transfer directly. The JACK compatibility layer itself adds negligible per-callback work. And thread placement, especially on hybrid Intel/AMD CPUs, can dominate every other tuning decision. + +For this project the JACK-versus-PipeWire question is settled: PipeASIO made Live a native PipeWire client, which also fixed hotplug and port-visibility issues JACK could not (`notes/ABLETON-WINE-PIPEASIO.md:9-21`, `notes/ABLETON-WINE-AUDIO-HOTPLUG.md:12-19`). The transferable lesson from the scsynth data is different: verify which cores Live's and PipeASIO's threads land on, and consider pinning, before touching kernels. + +## Native Linux DAW reference points + +Native DAWs show what the same hardware can do without Wine in the path. + +| Reference | Latency data point | Source | +|---|---|---| +| JACK math (applies to any DAW) | 128 frames, 2 periods, 48 kHz: 2.7 ms capture + 5.3 ms playback ≈ 8 ms round trip; "comparable to a stage monitor 2-3 m from the ear" | https://wiki.archlinux.org/title/Professional_audio | +| REAPER native Linux | Community reports of 1.4/2.9 ms latency settings in REAPER's own audio device settings, which operate independently of PipeWire | https://forum.cockos.com/showthread.php?p=2628855, https://forum.cockos.com/showthread.php?p=2879612 | +| Ardour | Uses the same JACK/PipeWire-JACK stack; per-application quantum/rate settings let each program run its intended buffer without forcing the whole graph | https://discourse.ardour.org/t/ardour-and-pipewire-jack-quantum-sample-rate-settings/113166 | +| Bitwig Studio | Community comparison: moving from PipeWire back to real JACK let Pianoteq divide its buffer by 4, with a smaller Bitwig improvement | https://bbs.archlinux.org/viewtopic.php?id=304116&p=2 | +| SuperCollider scsynth | Native PipeWire backend sustains about 2500 voices versus 1500 through the JACK shim on one hybrid CPU — an artifact of core placement, not the shim | https://scsynth.org/t/experimental-native-pipewire-audio-backend-for-scsynth-linux-feedback-wanted/13292 | + +These are one-machine anecdotes and forum posts, not controlled benchmarks; treat the exact numbers as indicative. The consistent picture: native engines on tuned systems run stable at 128-frame buffers and below, and REAPER and Bitwig set buffer size inside the application, which then drives the graph — the same shape PipeASIO's quantum matching gives Live on PipeWire 1.6+. + +## Gaps between this repo and the ecosystem + +- Buffer size is fixed at 256 frames by default with a per-launch environment override; nothing measures or recommends a lower per-machine value (`notes/ABLETON-WINE-PIPEASIO.md:56-59`). +- The scheduler priority ladder (rtkit/PipeWire, RR 10, FIFO 15, wineserver) is unmeasured; the pending `taskset` comparison covers CPU count but not priority ordering or wineserver inversion (`notes/ABLETON-WINE-RT-SCHEDULING.md:43-81`). +- No tooling checks hybrid-core thread placement, governor, `threadirqs`, or RTTIME limits; `scripts/setup-realtime.sh` covers only `rtprio` (`scripts/setup-realtime.sh:13-23`). +- No round-trip latency measurement exists in the repo; `scripts/check-live-audio.sh` verifies the driver opens, not the latency it achieves (`scripts/check-live-audio.sh:26-60`). +- The PipeWire-older-than-1.6 path (no quantum matching) has no measured fallback behavior (`notes/ABLETON-WINE-PIPEASIO.md:20`, `notes/ABLETON-WINE-PIPEASIO.md:46-48`). +- Plugin process isolation does not exist inside Live; the external-host workaround is documented but untested (`notes/ABLETON-WINE-PLUGIN-BRIDGING.md:1-6`). + +## Key opportunities + +1. Run the pending scheduler comparison and add a wineserver-priority arm. Impact: high. Effort: low. Evidence: the comparison protocol already exists but is unrun (`notes/ABLETON-WINE-RT-SCHEDULING.md:43-81`), and the wineserver inversion hypothesis is already recorded (`notes/ABLETON-WINE-RT-SCHEDULING.md:38-41`); `scripts/setup-realtime.sh:23` notes the wineserver boost was left out for want of root. +2. Measure and lower the default buffer below 256 frames on capable hardware, with a documented per-interface floor (512 for USB, 128-256 for HDA). Impact: high (halves or quarters buffer latency). Effort: low. Evidence: current fixed 256-frame default (`notes/ABLETON-WINE-PIPEASIO.md:56-59`); quantum/latency table and per-interface guidance at https://oneuptime.com/blog/post/2026-03-02-configure-pipewire-low-latency-audio-ubuntu/view. +3. Add a round-trip latency probe to the tester kit using a loopback cable and a JACK-tool-style measurement through PipeWire. Impact: medium (turns "feels faster" into a number, feeding `bench/results.csv`). Effort: medium. Evidence: no RTL tooling exists in-repo (`scripts/check-live-audio.sh:26-60` checks only driver-open); the `jack_iodelay` method is standard (https://wiki.archlinux.org/title/Professional_audio, https://oneuptime.com/blog/post/2026-03-02-configure-pipewire-low-latency-audio-ubuntu/view). +4. Audit hybrid-core thread placement for Live, PipeASIO, PipeWire, and wineserver; add optional `taskset`/cpuset guidance or pinning to the launcher. Impact: high on affected CPUs. Effort: medium. Evidence: the scsynth benchmark showed a 40-50% CPU swing from efficiency-versus-performance core placement, erased by pinning (https://scsynth.org/t/experimental-native-pipewire-audio-backend-for-scsynth-linux-feedback-wanted/13292); the launcher already has CPU-count capping groundwork (`scripts/ableton-live:75-107`). +5. Extend `scripts/setup-realtime.sh` into a full host-audio audit: governor, `threadirqs`/rtirq, RTTIME limits, rtkit presence (GNOME 45+), swappiness, and PipeWire `rt.prio`. Impact: medium. Effort: low. Evidence: each item is a documented xrun cause (https://github.com/robbert-vdh/yabridge, https://wiki.archlinux.org/title/Professional_audio); the script currently covers only `rtprio` (`scripts/setup-realtime.sh:13-23`). +6. Define the behavior on host PipeWire older than 1.6: either require 1.6 for quantum matching or ship a `clock.force-quantum` fallback set at install time. Impact: medium. Effort: low. Evidence: matching requires 1.6 but the runtime accepts 0.3.56 (`notes/ABLETON-WINE-PIPEASIO.md:20`, `notes/ABLETON-WINE-PIPEASIO.md:46-48`); `pw-metadata` forcing is the documented runtime mechanism (https://oneuptime.com/blog/post/2026-03-02-configure-pipewire-low-latency-audio-ubuntu/view) and this repo's own troubleshooting already uses it (`scripts/check-live-audio.sh:57-60`). +7. Lock the PipeWire graph rate to the hardware rate (`default.clock.rate`/`allowed-rates`) so PipeWire never resamples Live or a returning device. Impact: medium (removes resampler CPU and a latency source). Effort: low. Evidence: PipeWire resamples rate-mismatched devices today (`notes/ABLETON-WINE-AUDIO-HOTPLUG.md:34-35`); allowed-rates configuration at https://oneuptime.com/blog/post/2026-03-02-configure-pipewire-low-latency-audio-ubuntu/view. +8. Test and document the external-plugin-host workflow (Carla via PipeWire) as the supported way to isolate unstable plugins from Live's audio thread. Impact: medium (stability, not latency). Effort: medium. Evidence: workflow documented but untested (`notes/ABLETON-WINE-PLUGIN-BRIDGING.md:1-6`); yabridge demonstrates the isolation-versus-IPC trade-off (https://github.com/robbert-vdh/yabridge). +9. Run the missing WineASIO-versus-PipeASIO xrun comparison under identical load to close the last evidence gap from the 2026-07-17 evaluation. Impact: low (confirms a decision already made). Effort: medium. Evidence: the gap is explicitly listed (`notes/ABLETON-WINE-PIPEASIO.md:87-95`). +10. Evaluate a full-preemption or PREEMPT_RT kernel as a supported configuration, with measured xrun deltas rather than a blanket recommendation. Impact: medium (worst-case jitter, not average speed). Effort: medium. Evidence: PREEMPT_RT is mainline since 6.12 (https://oneuptime.com/blog/post/2026-03-02-how-to-install-real-time-kernel-preempt-rt-on-ubuntu/view); vanilla kernels are adequate for most setups (https://wiki.archlinux.org/title/Professional_audio); RT has measurable scheduling overheads (https://patchew.org/linux/20251219101502.GB1132199@noisy.programming.kicks-ass.net/). diff --git a/notes/performance-moonshot/docs/MOONSHOT-OPPORTUNITIES.md b/notes/performance-moonshot/docs/MOONSHOT-OPPORTUNITIES.md new file mode 100644 index 00000000..13e1cff3 --- /dev/null +++ b/notes/performance-moonshot/docs/MOONSHOT-OPPORTUNITIES.md @@ -0,0 +1,179 @@ +# Moonshot opportunities: ranked performance and stability work + +Use this document to pick the next performance or stability task for the +ableton-wine stack, see what it costs, and see how to prove it worked. It +merges the eight performance-moonshot research documents into one ranked +list. Duplicates are folded into a single item with every source cited. + +Scope: Ableton Live 12 under the `wine-d2d1-nspa-11.13` fork, PipeASIO on +PipeWire, ntsync for synchronization. Citations are relative paths from the +repository root. Anything marked "Unverified:" is a claim the research did +not confirm. + +## Terms + +- **xrun**: an audio buffer under- or overrun; one missed processing cycle, + heard as a click or dropout. +- **DSP load**: Live's own audio-engine meter. It compares buffer processing + time with buffer playback time. Over 100 percent means a dropout. +- **wineserver**: Wine's single-threaded userspace coordinator process. + Classic NT synchronization waits cross a socket to it twice per call. +- **ntsync**: a Linux kernel driver (6.14+) that implements Windows NT + synchronization primitives in the kernel, so waits skip wineserver. +- **APC**: asynchronous procedure call, a callback Windows queues onto a + specific thread. Live's APC-coalescing thread burns 30-40% of one core at + idle; ntsync does not cover alertable waits or APC delivery. +- **quantum**: PipeWire's buffer size in frames per processing cycle. 256 + frames at 48 kHz is about 5.3 ms. +- **RTL**: round-trip latency; the time for a signal to enter the interface, + pass through software, and leave again. +- **SCHED_RR / SCHED_FIFO**: Linux realtime scheduling policies. The launcher + runs the whole Wine process tree under SCHED_RR 10; PipeASIO requests + SCHED_FIFO 15 for its data-loop thread. +- **LTO / ThinLTO**: link-time optimization; cross-module inlining at link + time. **PGO**: profile-guided optimization; recompiling with branch data + from a recorded run. **THP**: transparent huge pages; 2 MB memory pages + that cut address-translation misses. +- **PI**: priority inheritance; temporarily raising a lock holder's priority + to stop priority inversion. + +## Source documents + +| Key | Path | +|---|---| +| TECH | notes/performance-moonshot/PROJECT-TECHNOLOGIES.md | +| LIVE | notes/performance-moonshot/ABLETON-LIVE-TECHNICAL.md | +| SYNC | notes/performance-moonshot/PROTON-SYNC-AND-CPU.md | +| GFX | notes/performance-moonshot/PROTON-GRAPHICS-AND-RUNTIME.md | +| PATCHMAP | notes/performance-moonshot/OWN-FORK-PATCH-MAP.md | +| BUILD | notes/performance-moonshot/OWN-FORK-BUILD-AND-RUNTIME.md | +| FORKS | notes/performance-moonshot/OTHER-FORKS-SURVEY.md | +| AUDIO | notes/performance-moonshot/AUDIO-LATENCY-ECOSYSTEM.md | + +## Try first + +High impact, low-to-medium effort, measurable with the existing bench and +probe tooling. Ordered by impact per unit of effort; the top three are +T1-T3. + +| # | Opportunity | Impact | Effort | Sources | +|---|---|---|---|---| +| T1 | **Check `/dev/ntsync` at launch and close the host gap.** Warn loudly when the device is missing; detect a kernel >= 6.14 with the module unloaded and ship a `modprobe.d`/udev drop-in or exact load instructions via the setup scripts. Today the build gate guarantees the binary supports ntsync but nothing checks the host. Users without the device silently lose 4-50x sync throughput and burn ~45% of a core in wineserver at idle. | High | Low | SYNC, BUILD, TECH | +| T2 | **Run the deferred scheduling A/B and narrow realtime scope.** Execute the written 4-CPU `taskset` protocol (notes/ABLETON-WINE-RT-SCHEDULING.md). Arms: default RR 10, `ABLETON_RT=off`, wineserver `chrt -f` boost, RT narrowed to audio threads. Audit whether Wine maps Live's Windows thread priorities to Linux scheduling at all (Unverified), and check whether Proton 9.0-4's new-thread priority fix has an upstream equivalent. Targets the recorded priority-inversion shape: Live's RT threads make synchronous calls into a `SCHED_OTHER` wineserver. | High on low-core machines, medium elsewhere | Low (protocol and harness exist) | LIVE, SYNC, BUILD, AUDIO, FORKS | +| T3 | **Automate the bench harness and commit the reference set.** Capture `pw-top -b` ERR deltas and `pw-metadata` rate/quantum inside `scripts/bench-run.sh`; add startup-time and idle-CPU (APC-thread) columns; commit the reference set under `bench/`. Today the headline xrun metric is hand-entered and the "committed reference set" the protocol requires is not in the repo. Every other item on this page is judged by this harness. | High (enables all other items) | Low-medium | BUILD, TECH | +| T4 | **Buffer, quantum, and rate pass on PipeWire.** Measure and lower the fixed 256-frame default per interface class (USB floor ~512, HDA 128-256), with a documented per-interface recommendation. Verify quantum matching: it needs PipeWire 1.6+ but the runtime accepts 0.3.56, so older hosts get the 1024 default with no warning; warn or ship a `clock.force-quantum` fallback at install. Lock the graph rate to the hardware rate so PipeWire never resamples Live. Close the PipeASIO validation gaps: sample-rate change while open, single-rate hardware, controlled xrun comparisons. | High (halves or quarters buffer latency; a mismatched quantum is a direct xrun source) | Low-medium | AUDIO, BUILD, LIVE | +| T5 | **Compiler-flags comparison builds.** Adopt Proton's proven baseline (`-O2 -fwrapv -fno-strict-aliasing -march=nocona -mtune=core-avx2 -mfpmath=sse`); the build passes no CFLAGS at all today (scripts/container-build.sh:53-56). Then A/B `-O3` and `-march=x86-64-v2` (v2 is near-universal; ship v3/AVX2 as an opt-in variant). Kron4ek ships `-O3 -msse3`; CachyOS ships v3/v4+LTO. Optionally a newer gcc inside the same jammy image (keeps the glibc 2.35 floor). Most CPU in a session burns inside Live.exe and plugins, not Wine DLLs, so expect small gains; measure, do not assume. Gate each variant on the relocation gate, build audit, and a bench pair. | Low-medium | Low | GFX, BUILD, FORKS, TECH | +| T6 | **Audit hybrid-core thread placement and publish affinity guidance.** Check which cores Live, PipeASIO, PipeWire, and wineserver threads land on. The scsynth benchmark measured a 40-50% CPU swing from E-core versus P-core placement, erased by pinning. Publish `taskset`/cpuset guidance for Intel P/E and Ryzen X3D hosts; optional launcher pinning. The launcher already honors cgroup cpusets (scripts/ableton-live:85-97). | High on affected CPUs | Low for guidance, medium for pinning | AUDIO, SYNC, BUILD | + +## Worth a spike + +Promising but uncertain, or medium effort. Unranked; pick by available +hardware and time. + +| # | Opportunity | Impact | Effort | Sources | +|---|---|---|---|---| +| S1 | **Land the `WINE_CPU_TOPOLOGY` consumer patch.** The launcher computes and exports an 8-CPU cap but marks it inert; the ntdll/wineserver consumer is the missing half. Port Proton's patch. Fixes worker-pool oversizing on >8-core hosts and enables V-Cache/P-core pinning. | Medium on >8-core hosts | Medium | SYNC, BUILD, TECH | +| S2 | **A/B DXVK for Live's D3D11 UI, wined3d-Vulkan first.** Prefix-level experiment, no rebuild; the vendored winetricks carries dxvk verbs. Risks are concrete: patches 0055/0058/0059 become dead code under Vulkan swapchains, and Live's device-name gate (patches 0057/0061) may reject DXVK's adapter names (Unverified: whether the check passes on every supported GPU family). Abandon if the gate greys out or the WebView2 panes regress. | Medium if it works, likely low (2D UI) | Medium | GFX | +| S3 | **Extend the direct GL present path to `WS_POPUP` and `WS_CHILD` windows.** Kills the remaining ~650 MB/s-class copy traffic for Settings, the auth dialog, and embedded plugin editors. Must solve the black-before-first-input popup issue. | Medium | Medium | LIVE | +| S4 | **Implement a real `dxgi_output_WaitForVBlank`.** The current semi-stub (dlls/dxgi/output.c:371) only calls `Sleep(16)`; it paces all Max for Live device redraw (meters, jsui). Unverified: whether it costs smoothness or CPU in normal use. | Medium | Medium | LIVE | +| S5 | **Extend `scripts/setup-realtime.sh` into a full host-audio audit.** Governor, `threadirqs`/rtirq, RTTIME limits and rtkit presence (GNOME 45+ forces rtkit, whose 200 ms RTTIME cap throttles audio threads), swappiness, PipeWire `rt.prio`. Automate IRQ affinity instead of advising it; fail the realtime check when `threadirqs` is missing rather than printing once at setup. | Medium | Low-medium | AUDIO, BUILD | +| S6 | **ThinLTO on the clang PE side, after T5 lands.** clang 21 + lld is already the PE toolchain, so ThinLTO needs no new dependency. Unverified: whether the Wine PE build survives `-flto=thin`; winebuild-generated assembly and `.spec` handling are the usual failure points. Gate on the relocation gate plus a bench pair. Valve avoids LTO, so there is no upstream proof. | Medium-high | Medium | BUILD, GFX | +| S7 | **Add a round-trip latency probe.** Loopback cable plus a `jack_iodelay`-style measurement through PipeWire, packaged for the tester kit. Turns "feels faster" into a number that feeds `bench/results.csv`. No RTL tooling exists in the repo. | Medium | Medium | AUDIO, TECH | +| S8 | **THP experiment on the audio workload.** Compare xruns and `perf` TLB-miss rates with THP `madvise` versus `never` on a large session; Live holds multi-GB sample buffers in ordinary pages. If it helps, ship a launcher-side wrapper, not a system-wide change. No upstream implementation exists to copy. Unverified benefit. | Low-medium | Low | GFX | +| S9 | **Verify the upstream non-ntsync in-process fallback.** Unverified: community guides claim Wine >= 10.15 has an eventfd in-process fallback when `/dev/ntsync` is absent; this repo's header-less fallback builds paid full wineserver round trips, but that build may have lacked the configure gate. If the fallback is real and not header-gated, users on kernels < 6.14 get a free win. | Medium | Low | SYNC | +| S10 | **Test and document the external-plugin-host workflow.** Carla over PipeWire is the documented-but-untested way to isolate unstable Linux-native plugins from Live's audio thread. Live hosts plugins in-process, so a plugin fault is a Live fault; no Wine tuning changes that. | Medium (stability) | Medium | AUDIO, LIVE | +| S11 | **Benchmark Wine file I/O for sample streaming.** Disk overloads cause dropouts on the real-time path; no repo measurement exists of Wine file-I/O latency versus Windows. | Unknown, potentially medium | Low-medium | LIVE | +| S12 | **Resolve the tempo-ramp export difference (issue 101).** Exports render ~0.08% shorter than on Windows; leading hypothesis is a Live-version mismatch, unproven. The export test needs no Windows machine. | Low-medium | Low | LIVE, PATCHMAP | +| S13 | **Measure and document Link Audio (Live 12.4).** Timed network audio joins the audio clock domain; it needs the same xrun-style measurement as PipeASIO before anyone claims support. Documentation arm only; the implementation arm is M4. | Medium | Low | LIVE | +| S14 | **Make wineserver persistent across launches (`wineserver -p`).** Cuts cold-start time and the per-launch `wineboot`. No steady-state audio effect. | Low-medium (launch feel) | Low | BUILD | +| S15 | **Evaluate a PREEMPT_RT or lowlatency kernel as a supported configuration.** PREEMPT_RT is mainline since 6.12. It buys worst-case scheduling jitter, not average speed, and has measurable scheduling overheads. Recommend only with measured xrun deltas, not blanket advice. | Medium | Medium | AUDIO, BUILD | +| S16 | **Evaluate proton-cachyos' `winepipewire.drv`** as an mmdevapi-level PipeWire reference for the non-ASIO paths (WebView2 pane audio, plugin preview sound). | Low | Medium | FORKS | +| S17 | **Audit Wine font APIs for Windows-lax behaviour Max relies on.** The vendored-font fix removed one M4L hang trigger, not the flaw; Wine's honesty about font failure turns Windows-latent Max defects into Live hangs. | Medium (prevents the next M4L hang class) | Medium | LIVE | +| S18 | **Revisit the WebView2 SwiftShader flags once dcomp compositing improves.** CPU rendering of every visible Learn pane is a standing UI-thread cost, accepted for correctness. Low effort to re-test; the fix depends on upstream dcomp work. | Medium (UI) | Low to re-test | BUILD | +| S19 | **Run the missing WineASIO-versus-PipeASIO xrun comparison** under identical load. Confirms a decision already made; closes the last evidence gap from the 2026-07-17 evaluation. | Low | Medium | AUDIO, LIVE | +| S20 | **Identify and implement the `wmvcore` export Live 11 calls.** Live 11 is experimental, so this is low priority. | Low | Medium | LIVE | +| S21 | **Standing practices.** Keep every new performance patch behind an environment toggle (the Proton/GE pattern; this repo's `WINE_DISABLE_GL_PRESENT` is the precedent). Track Valve Proton experimental, proton-cachyos, Proton-EM, and vkd3d-proton present timing as a watch list, not as patch sources. | Low | Low | FORKS, GFX | + +## Moonshots + +High effort or high risk, potentially large payoff. + +| # | Opportunity | Impact | Effort | Sources | +|---|---|---|---|---| +| M1 | **APC / alertable-wait fast path.** Deliver same-process user APCs through ntsync's alert event instead of wineserver round trips. Must preserve FIFO ordering, special APCs, and I/O completion ordering. Step zero: read wine-staging's `ntdll-APC_Performance` set before writing anything (its mechanism is Unverified; the definition file did not fetch). Needs a new `apcprobe`. This is the largest measured CPU sink outside the driver: 30-40% of one core at idle, plus per-APC wineserver serialization as the playback-fault hypothesis. ntsync covers handle waits only; alertable sleeps and APC delivery still cross wineserver. The sync/threading area is otherwise untouched by the patch series. | High | High | LIVE, SYNC, FORKS, PATCHMAP, BUILD | +| M2 | **Port Wine-NSPA's portable client-side work.** The portable subset: message rings with empty-poll caching, local events/timers/sections, shared-state readers (zero-time waits without a server round trip), TEB hot-state caching, cacheline-shaped userspace sync, AVX2 string loops, `mlockall`/hugepage heap backing, and PI for `CRITICAL_SECTION` (targets the wineserver priority inversion directly). Unverified: whether full 11.x sources or patch files are public; the repo publishes design and validation documents, so verify before planning ports. The kernel-dependent items (IPC overlay, PI ntsync) assume the custom Linux-NSPA kernel and are out of scope. Port precedent exists: patches 0002-0003 came from nine7nine's tree. | High | High | FORKS, SYNC, PATCHMAP | +| M3 | **PGO for the PE build.** Needs a scripted, replayable Live workload to profile inside the container; clang PGO on Wine PE is unexplored here. No `-fprofile` flag exists anywhere in the repo, and Valve ships no PGO. | Medium | High | BUILD, GFX | +| M4 | **Implement Link Audio (Live 12.4).** Multichannel timed audio over LAN arrives as an input in Live and joins the audio clock domain. Implement only if the S13 measurement shows demand and feasibility. | Medium | High | LIVE | + +## Do not pursue + +Techniques the research rejected. Each line is the decision of record. + +| Technique | Reason | Sources | +|---|---|---| +| esync / fsync / fastsync | Superseded by ntsync, which is already shipped, has exact NT semantics, and has no fd pressure. Nothing there is worth resurrecting. | SYNC | +| `-DontCombineAPCs` | Removes the idle-core burn but starves playback; reverted and stripped from releases. The fix is M1, not the flag. | LIVE, PATCHMAP | +| Wineserver replacement or rewrite | No credible effort exists as of August 2026; the working strategy is shrinking wineserver's role, not replacing it. | SYNC | +| vkd3d-proton | No D3D12 anywhere in this workload. Watch item only. | GFX | +| DXVK-NVAPI | Game latency-reduction and upscaling features a DAW UI cannot use. | GFX | +| Fossilize shader pre-caching | Vulkan-only; this stack's GPU work is OpenGL and software rendering, and Live's fixed shader set has no stutter problem. | GFX | +| pressure-vessel container | The pinned relocatable tarball already provides library isolation; a DAW needs host PipeWire, ALSA, USB, RT scheduling, and portals, so a container adds failure modes for no gain. | GFX | +| gamescope and frame-latency tooling | Live is a multi-window desktop app, not a fullscreen surface; the equivalent present-path work already shipped in patches 0055/0058/0059. | GFX | +| GE-Proton as a runtime | Game-targeted and Steam-container-bound; supported outside Steam only through umu. | GFX, FORKS | +| FAudio / winepulse tuning | Live's engine rides ASIO -> PipeASIO -> PipeWire; winepulse carries only incidental sound. | GFX, AUDIO | +| WineASIO / JACK return | PipeASIO superseded it: WineASIO summed inputs to mono, showed high latency under load, and lost PipeWire links on hotplug. | AUDIO | +| wine-rt-style `WINE_RT` patch | Superseded by the launcher's `chrt` policy. | FORKS | +| wine-wayland migration | The patch series is deeply winex11-shaped; the Wayland driver is still landing windowing basics in mid-2026. Re-check later. | FORKS | +| wine-tkg / GE game patches (FSR, fs-hack, raw input, compositor bypass, media foundation) | Game-only; Live's media import already works through winegstreamer. | FORKS | +| hangover / box64 | x86-on-ARM emulation; this project targets x86-64 only. | FORKS | +| Allocator swaps and Proton heap options | Game bug workarounds, not optimizations. | GFX | +| Blanket PREEMPT_RT recommendation | Buys worst-case jitter, not speed, with real scheduling overheads; only S15's measured deltas justify a recommendation. | AUDIO | + +## Measurement plan + +The project's evidence standard is a committed before/after pair from +`scripts/bench-run.sh` under fixed reference conditions. No performance +claim ships without one. + +### Existing tools + +| Tool | What it measures or gates | +|---|---| +| `scripts/bench-run.sh` | One CSV row per run into `bench/results.csv`. Automated: `wined3d_cs_pct` (60 s of per-thread `top` samples) and `wineserver_ctxt_delta` (60 s of `/proc` counters). Operator-entered: `xruns_5min` (pw-top ERR delta) and `dsp_load_pct` (Live's meter). | +| `scripts/check-ntsync.sh` | ntsync semantics probe, `/dev/ntsync` presence, wineserver open-fd check, wineserver context-switch delta. | +| `scripts/check-live-audio.sh` | Live opens PipeASIO without a FatalError or hang; sample-rate line present; points at `pw-metadata` for forced quantum. | +| `scripts/check-m4l-fonts.sh` | M4L font-fallback hang regression. | +| `scripts/build-audit.sh` | Patch-stack provenance and per-patch binary fingerprints; the relocation gate (scripts/container-build.sh:240-265) proves the packaged tree runs. | +| `beta/tester-kit/run-session` + `beta/tester-kit/probes/src/` | Redacted system report plus checksum-verified probes: `ntsyncprobe.c` (sync throughput table), `stresstest.c`, `resizeprobe.c`, `menumeasure.c`, `glchild.c`, `portalprobe.c`, `midihot.c`, `dcompspy.c`. | +| `tools/` | Diagnostic probes: `midihot`, `linkprobe`, `mousespy`, `metricprobe2`, `stresstest`, `m4l-hang-capture.sh`, `m4l-font-audit.py`. Diagnostics, not benchmarks. | +| External | `pw-top`, `pw-metadata`, `top -H`, `perf`. | + +### Verification per top-tier opportunity + +| Opportunity | How to verify | Baseline to close first | +|---|---|---| +| T1 `/dev/ntsync` launch check | `check-ntsync.sh` verdict on a host with the module unloaded; bench pair on `wineserver_ctxt_delta`; `ntsyncprobe` throughput table | None | +| T2 Scheduling A/B | `bench-run.sh` pairs across arms (`ABLETON_RT=off`, default, wineserver boost, narrowed RT) under the written 4-CPU `taskset` protocol; xrun and DSP rows per arm | Committed reference set (T3); a low-core machine for the comparison | +| T3 Bench automation | The harness itself; a `bench/` directory with the reference set and rows | Self-contained | +| T4 Buffer/quantum/rate | `check-live-audio.sh` at each buffer size; `pw-metadata` force-quantum readout; bench pairs at 512/256/128/64 frames | Automated xrun capture (T3); RTL probe (S7) for absolute latency numbers | +| T5 Compiler flags | Relocation gate plus `build-audit.sh` for correctness; bench pairs on `wined3d_cs_pct` and `wineserver_ctxt_delta`; `ntsyncprobe` throughput; the present-bandwidth method from issue 91 | Committed reference set (T3) | +| T6 Hybrid-core placement | Bench pairs pinned versus unpinned; `top -H` inspection of which cores Live, PipeASIO, and wineserver threads occupy | A hybrid-core machine for the comparison | + +### Missing baseline + +These gaps block or weaken measurement today: + +- No committed `bench/` directory or reference set. The harness protocol + requires a committed reference set; the repo does not contain one. + Unverified: testers may hold it privately. +- The headline xrun metric is operator-entered. No automated `pw-top -b` + capture exists. +- No RTL or callback-period-jitter measurement exists anywhere in the repo. +- No startup-time, idle-CPU (APC-thread), or UI-responsiveness metrics. +- The RT policy's effect on low-core-count systems is unmeasured. +- Wine file-I/O latency versus Windows is unmeasured. +- The WineASIO-versus-PipeASIO controlled comparison was never run. +- Bench rows do not record Live, WebView2, or GPU-driver versions, although + those are uncontrolled variables: WebView2 Evergreen self-updates inside + the prefix and regressed rendering once already. Record versions per row. +- No documented per-interface buffer floors. diff --git a/notes/performance-moonshot/docs/OTHER-FORKS-SURVEY.md b/notes/performance-moonshot/docs/OTHER-FORKS-SURVEY.md new file mode 100644 index 00000000..b7d880ef --- /dev/null +++ b/notes/performance-moonshot/docs/OTHER-FORKS-SURVEY.md @@ -0,0 +1,127 @@ +# Other Wine forks: performance and stability survey + +This document helps the reader decide which performance and stability work from other Wine forks is worth porting into this repository's patch series. It surveys GE-Proton, wine-tkg and proton-tkg, wine-staging, Kron4ek Wine-Builds, CachyOS builds, Valve Proton, Proton-EM, the upstream wine-wayland driver, Wine-NSPA, and the older real-time audio lineage. ENCORE, the other Ableton-focused fork, is covered separately in [ABLETON-WINE-ENCORE-REVIEW.md](../ABLETON-WINE-ENCORE-REVIEW.md). + +## How this project adopts outside work + +The build unpacks a pinned Wine base (giang17/wine `d2d1-dcomp-11.13` @ `5c23dd1c`, `patches/BASE.txt:3-6`) and applies 62 numbered patch files in lexical order with `git am --3way` (`patches/BASE.txt:14`, `scripts/container-build.sh:37-45`). Any adopted idea must therefore land as one or more clean `.patch` files against that tree. The precedent is selective porting: the ENCORE review took three changes and rejected the rest (`notes/ABLETON-WINE-ENCORE-REVIEW.md:1-6`). + +Already covered, so forks offering these add nothing: + +- ntsync, the in-kernel NT synchronization driver. The build vendors the UAPI header (`Containerfile:94-98`) and hard-fails if ntsync is missing from either wineserver or ntdll (`scripts/container-build.sh:108-123`). Adding ntsync "reduced reported wineserver idle CPU use" (`CHANGELOG.md:246`). +- Real-time scheduling. The launcher runs Wine under `SCHED_RR` priority 10 and PipeASIO requests `SCHED_FIFO` 15 for its data loop (`notes/ABLETON-WINE-RT-SCHEDULING.md:1-11`). +- A low-latency ASIO path. PipeASIO is built against this exact Wine (`scripts/container-build.sh:131-176`); ASIO (Audio Stream Input/Output) is the low-latency audio driver API DAWs use. +- A direct GL present path for Live's D3D11 swapchains, behind an env toggle (`patches/BASE.txt:167`). + +## Which forks carry what + +| Fork | Beyond upstream Wine | Targets | DAW relevance | Port difficulty | +|---|---|---|---|---| +| GE-Proton | Game fixes, media foundation, NVAPI, ntsync toggle | Games | Low | High | +| wine-tkg / proton-tkg | Build system with perf patch toggles | Games | Medium (mechanism, some patches) | Low–medium | +| wine-staging | ~100 experimental patch sets; a few perf-named | General + games | Medium | Low–medium | +| Kron4ek Wine-Builds | Binaries; `-O3 -msse3` flags | General | Medium (flags only) | Trivial | +| CachyOS proton-cachyos | Bleeding-edge Proton + winepipewire.drv + v3/LTO packaging | Games | Medium (audio driver) | Medium | +| Valve Proton experimental | Per-game fixes, CPU topology, thread priorities | Games | Low (watch only) | High | +| Proton-EM | winewayland.drv improvements | Games | Low (watch only) | High | +| wine-wayland (upstream) | Native Wayland driver | General | Low in 2026 | Very high | +| Wine-NSPA | RT/audio fork: PI, IPC, client-side NT, io_uring | Pro audio | Highest | Medium–high | +| wine-rt / wineasio / yabridge | Historical RT patches; plugin bridge | Pro audio | Superseded / reference | n/a | +| hangover / box64 | x86 on ARM64 emulation | ARM64 | None (x86-64 only) | n/a | + +## GE-Proton: game fixes, not audio + +GE-Proton carries, beyond Valve's Proton: media-foundation patches for video playback, AMD FSR upscaling patches, NVIDIA CUDA/NVAPI support, raw-input mouse patches, the protonfixes per-game fix system, selected upstream backports and wine-staging picks, and ntsync enablement when the kernel supports it (https://github.com/GloriousEggroll/proton-ge-custom). Its patches apply through one script, `patches/protonprep-valve-staging.sh`, over a Valve Proton Wine tree (same README). + +Relevance to a real-time DAW: low. The media-foundation work overlaps functionality this project already ships through winegstreamer (`scripts/container-build.sh:96-102`). The rest is game compatibility. Two transferable patterns, not patches: every behavioral change sits behind an environment toggle (its README lists `PROTON_NO_NTSYNC`, `PROTON_HEAP_DELAY_FREE`, `PROTON_NO_WRITE_WATCH`), and the full patch queue lives in one ordered script. Adoption difficulty: high. The patches target Valve's Wine fork, not mainline 11.13, so each needs a rebase; the value does not justify it. + +## wine-tkg and proton-tkg: a build system with toggles + +wine-tkg is a build system that assembles a Wine tree from a config file; proton-tkg wraps it for Proton builds (https://github.com/Frogging-Family/wine-tkg-git). The current config options relevant to performance (https://raw.githubusercontent.com/Frogging-Family/wine-tkg-git/master/wine-tkg-git/customization.cfg): + +- `_use_ntsync`: ntsync support; Wine 10.11+ and kernel 6.14+. +- `_use_esync`, `_use_fsync`, `_use_fastsync`: legacy out-of-tree sync primitives (eventfd and futex based), all marked legacy and superseded by ntsync. Skip. +- `_FS_bypass_compositor`: bypasses the compositor for fullscreen games to reduce stutter. Live runs windowed; skip. +- `_proton_fs_hack`, `_proton_rawinput`, GE game patches, `_proton_winevulkan`: game-specific; skip. +- `wine-tkg-userpatches`: a drop-in user patch directory. Same mechanism as this repo's `patches/`; nothing to adopt. + +Relevance: medium as a patch source. The generated trees (Tk-Glitch/wine-tkg, wine-proton-tkg) make it possible to lift single commits as `.patch` files. Most of the queue is game work. Adoption difficulty: low to medium per patch. + +## wine-staging: which patch sets touch performance + +wine-staging is Wine's experimental patch queue, a staging area for work not yet merged upstream. The current master list is at https://github.com/wine-staging/wine-staging/tree/master/patches. Most sets are correctness or application-compatibility fixes. The sets with performance in scope: + +| Patch set | What it does | DAW relevance | +|---|---|---| +| `ntdll-APC_Performance` | Name indicates APC overhead reduction; the exact mechanism is Unverified (its definition file did not fetch) | High if it overlaps this repo's APC problem (see below) | +| `server-PeekMessage` | Message-order correctness fix ("GetMessage should remove already seen messages with higher priority", bug 28884: https://raw.githubusercontent.com/wine-staging/wine-staging/master/patches/server-PeekMessage/definition) | Low; not a perf patch | +| `gdiplus-Performance-Improvements` | gdiplus speed-ups | Low; Live's UI is not gdiplus-based, some plugin editors are | +| `wined3d-unset-flip-gdi` | Presentation/GDI-flip handling in wined3d | Medium; adjacent to this repo's present-path work (`patches/BASE.txt:167`) | +| `shell32-IconCache`, `dxgi_getFrameStatistics` | Icon cache; DXGI frame statistics API | Low | +| `dsound-EAX` | Positional audio for games | None | + +The APC set matters here. APC stands for asynchronous procedure call, a callback Windows queues onto a thread for delivery at its next alertable wait. Live's APC-coalescing thread burns 30–40% of a core at idle (`notes/ABLETON-WINE-APC-COALESCING.md:3`), and this repo has a written, unimplemented proposal to deliver same-process user APCs through the ntsync alert event instead of wineserver (`notes/ABLETON-WINE-APC-COALESCING.md:32-41`). Read `ntdll-APC_Performance` before writing that patch. Adoption difficulty for any staging set: low to medium; staging patches are formatted against the matching upstream Wine and this base is Wine 11.13 plus a fork's dcomp work, so context drift is the main risk. + +## Kron4ek Wine-Builds: compiler flags, not patches + +Kron4ek publishes vanilla, staging, staging-tkg, and proton-flavored Wine binaries (https://github.com/Kron4ek/Wine-Builds). The transferable content is the build configuration, not patches: amd64 builds use `-march=x86-64 -msse3 -mfpmath=sse -O3` with `--without-oss --disable-winemenubuilder --disable-tests`, targeting glibc 2.27 (same README). This project's container passes no `CFLAGS` or `-march`; it uses Wine's configure defaults (`scripts/container-build.sh:53-56`). An `-O3` comparison build is a cheap experiment. Adoption difficulty: trivial. + +## CachyOS: proton-cachyos and wine-cachyos + +proton-cachyos tracks Valve's Proton experimental bleeding-edge, applies wine-staging, and imports winewayland.drv improvements from Proton-EM (https://github.com/CachyOS/proton-cachyos/releases, May 2026 entry). Two items stand out for an audio workload: + +- `winepipewire.drv`, a native PipeWire backend for mmdevapi (the standard Windows audio API above ASIO), enabled by default, with a documented note that full `+pipewire` tracing perturbs audio timing (https://github.com/CachyOS/proton-cachyos). This repo serves Live through PipeASIO, but an mmdevapi-level PipeWire path covers everything that is not ASIO. +- Distro-level optimization: CachyOS rebuilds packages for x86-64-v3/v4 with LTO (https://wiki.cachyos.org/features/optimized_repos/). wine-cachyos exists as a separate build with ntsync support (https://discuss.cachyos.org/t/ntsync-in-latest-proton-cachyos-wine-cachyos/5254); its exact patch list is Unverified — the repository README is the stock Wine README (https://github.com/CachyOS/wine-cachyos). + +Relevance: medium. Adoption difficulty: medium for winepipewire.drv (one driver, new code rather than a conflict with the existing series), trivial for the compiler-flag idea. + +## Valve Proton experimental and bleeding-edge: a watch list + +Proton 11.0-1 rebased on Wine 11.0 and ships updated DXVK, vkd3d-proton, and Wine Mono; Proton experimental tracks it plus per-game fixes, current as of 2026-07-28 (https://github.com/ValveSoftware/Proton/wiki/Changelog). ntsync is the headline sync change: SteamOS 3.7.20 loads the ntsync module by default (https://www.phoronix.com/news/Steam-OS-Beta-NTSYNC, https://www.gamingonlinux.com/2026/01/steamos-3-7-20-adds-the-ntsync-driver-to-help-improve-some-game-performance/), and Proton 11 brings it to the Steam ecosystem (https://www.tweaktown.com/news/111106/valves-proton-11-beta-unlocks-more-playable-games-and-boosts-performance-for-steam-deck-and-linux-fans/index.html). + +Three Proton changelog entries touch thread and CPU behavior rather than games: "Fixed Proton not setting priorities correctly for new threads" (9.0-4), "Fixed CPU topology override issues on machines with more than 32 logical cores" (10.0-1), and per-game core-count limits for old titles (9.0-1) (all: https://github.com/ValveSoftware/Proton/wiki/Changelog). Relevance: low for adoption — the fork's delta from mainline is huge and Steam-runtime-bound — but it is the fastest-moving public consumer of Wine 11 sync work. Treat it as an early-warning feed. + +## Proton-EM: where Wayland fixes land first + +Proton-EM is Etaash Mathamsetty's Proton fork carrying winewayland.drv improvements, HDR, and FSR4 work (https://github.com/Etaash-mathamsetty/Proton). proton-cachyos regularly imports its Wayland patches (https://www.gamingonlinux.com/2026/05/proton-cachyos-11-adds-initial-optiscaler-integration-and-lots-of-other-fixes/). Its docs (`docs/EM-ADDITIONS.md`, `docs/CHANGES.md` in that repo) are the most current public record of what winewayland still cannot do. Games-focused; relevance here is as documentation for the Wayland question below. + +## wine-wayland in 2026: not a migration target + +The upstream Wayland driver is improving but still acquiring windowing basics in mid-2026: Wine 11.0 shipped "better Wine Wayland driver support" (https://www.phoronix.com/news/Wine-11.0-Released), Wine 11.11 added layered windows and min/max size hints (https://www.phoronix.com/news/Wine-11.11-Released), Wine 11.12 added fractional scaling (https://www.phoronix.com/linux/WINE news archive, 29 June 2026 entry), and alpha-modifier support landed the same month (https://www.phoronix.com/news/Wine-Wayland-Alpha-Modifier). Downstream consumers still treat it as experimental: proton-cachyos documents white-window failures for CEF/Electron apps and notes Proton 11 removed Proton 10's automated Wayland hacks (https://github.com/CachyOS/proton-cachyos); GE-Proton notes Steam overlay and Steam Input do not work with the driver (https://github.com/GloriousEggroll/proton-ge-custom). + +This project's series is deeply winex11-shaped — winex11 changes run through patches 0002–0017 and recur at 0039, 0042, 0053, and 0062 (`patches/BASE.txt:14` and the per-patch provenance list) — and Live's validated configuration is XWayland (see the resize and menu notes under `notes/`). Migration would port or discard most of that work for an unclear gain. Verdict: keep winex11; re-check the driver after it stops landing per-release windowing basics. + +## Wine-NSPA: the pro-audio fork to mine + +Wine-NSPA (nine7nine) is a PREEMPT_RT-focused fork of Wine 11.8 for pro audio — PREEMPT_RT is the kernel patch set that makes Linux fully preemptible for real-time workloads (https://github.com/nine7nine/Wine-NSPA). Its documented work, per the README's architecture index and status: + +- Priority inheritance (PI) for `CRITICAL_SECTION` and Win32 condition variables, via a bundled librtpi re-implementation. PI temporarily raises a lock holder's priority to prevent priority inversion — directly relevant to this repo's noted risk that Live's real-time threads outrank the `SCHED_OTHER` wineserver they synchronously call (`notes/ABLETON-WINE-RT-SCHEDULING.md:15-20`). +- A kernel-mediated wineserver IPC layer ("gamma channel dispatcher") with aggregate-wait and burst drain, plus a kernel-side ntsync PI overlay in the companion Linux-NSPA kernel (https://github.com/nine7nine/Linux-NSPA-pkgbuild). +- Client-side NT surfaces: local events, local timers, local files, local sections, and thread/process shared-state readers that turn some waits into "zero-time" waits without a wineserver round trip. +- `io_uring` (Linux's shared-ring async I/O interface) for file and socket I/O. +- Hot-path work: message rings with empty-poll caching, TEB (thread environment block, per-thread NT state) hot-state caching, cacheline-shaped userspace sync, AVX2 string/Unicode loops. +- RT memory: `mlockall()`, automatic hugetlb promotion, heap hugepage backing. +- Audio: winejack and nspaASIO drivers; embedding protocols that let winelib hosts (winelib is Wine's library for building Unix applications against the Win32 API) embed Wine HWND plugin editors over X11 or Wayland; a Yabridge-NSPA bridge fork. +- Stated validation: native ntsync suite 3 PASS / 0 FAIL, PE matrix 32 PASS / 0 FAIL / 0 TIMEOUT (`v9-validation-default`). + +This is the only surveyed fork built for the same workload class as this project, and the relationship already exists: patches 0002–0003 here are winex11 changes from nine7nine/wine-nspa-src commits (`patches/BASE.txt:34-36`). Community reports also credit the Wine-NSPA ecosystem's Ableton Options.txt tuning with large CPU reductions under Wine (https://github.com/nine7nine/Wine-NSPA/issues/4). Two cautions. First, the 11.x repository publishes "design, architecture, and validation documentation"; whether full 11.x sources or patch files are public is Unverified — verify before planning ports. Second, several items (kernel IPC overlay, PI ntsync) assume the custom Linux-NSPA kernel, which this project does not ship; the client-side items (message ring, empty-poll caching, local events/timers, `mlockall`) are the portable subset. Adoption difficulty: medium-high per item, high for the kernel-dependent items. + +## wine-rt, wineasio, yabridge: the real-time lineage + +- wine-rt: a 2013-era patch that gave Wine threads `SCHED_FIFO` via the `WINE_RT` environment variable (https://github.com/PlayOnLinux/wine-patches/blob/master/custom/RealTime/rt.patch); KXStudio shipped an rt-patched Wine for audio work (https://forum.winehq.org/viewtopic.php?t=32742). Superseded by this repo's launcher-level `chrt` policy (`notes/ABLETON-WINE-RT-SCHEDULING.md:1-11`). Nothing to adopt. +- wineasio: the classic ASIO-to-JACK driver. This project ships PipeASIO instead (see `notes/ABLETON-WINE-PIPEASIO.md`). Nothing to adopt. +- yabridge: runs Windows VST2/VST3/CLAP plugins in Wine for native Linux hosts, bridging over shared memory and UNIX sockets with under 1 ms added latency in one 2026 account (https://bonnef.in/posts/linux-music-production/, project at https://github.com/robbert-vdh/yabridge). Live loads plugins in-process here, so the bridge is not needed; its Yabridge-NSPA fork is worth tracking as a bellwether for Wine-NSPA's RT rules. + +## hangover and box64: out of scope + +hangover pairs Wine with the FEX or Box64 emulators to run x86 Windows applications on ARM64 Linux; Hangover 11.0 released alongside Wine 11.0 in January 2026 with QEMU support removed (https://www.phoronix.com/news/Hangover-11.0-Released, https://github.com/AndreRH/hangover). This project builds for i386 and x86_64 only (`scripts/container-build.sh:55`) and targets x86-64 hosts. Not applicable. + +## Key opportunities + +1. **Mine Wine-NSPA's portable client-side work** — message ring, empty-poll caching, local events/timers, shared-state waits, `mlockall` — as individual patches, starting with whichever maps to the largest measured wineserver load. Impact: high. Effort: high. Evidence: https://github.com/nine7nine/Wine-NSPA (documented highlights); existing port precedent at `patches/BASE.txt:34-36`. +2. **Read wine-staging's `ntdll-APC_Performance` before implementing the proposed same-process APC bypass.** Impact: high (targets a measured 30–40% idle core and suspected playback xruns). Effort: medium. Evidence: `notes/ABLETON-WINE-APC-COALESCING.md:3` and `:32-41`; patch set listed at https://github.com/wine-staging/wine-staging/tree/master/patches. +3. **Benchmark an `-O3 -march=x86-64-v3` build against the current default-flags build.** Impact: medium. Effort: low. Evidence: no `CFLAGS`/`-march` in `scripts/container-build.sh:53-56`; Kron4ek ships `-O3 -msse3` (https://github.com/Kron4ek/Wine-Builds); CachyOS ships v3/v4+LTO repos (https://wiki.cachyos.org/features/optimized_repos/). +4. **Audit Windows thread-priority mapping against the launcher's `SCHED_RR` inheritance, and check whether Proton's new-thread priority fix has an upstream equivalent.** Impact: medium. Effort: medium. Evidence: "Fixed Proton not setting priorities correctly for new threads" (https://github.com/ValveSoftware/Proton/wiki/Changelog, 9.0-4); priority-inversion risk noted at `notes/ABLETON-WINE-RT-SCHEDULING.md:15-20`. +5. **Evaluate proton-cachyos' `winepipewire.drv` as an mmdevapi-level PipeWire reference for non-ASIO audio paths.** Impact: low. Effort: medium. Evidence: enabled by default with latency-tuning notes at https://github.com/CachyOS/proton-cachyos. +6. **Keep every new performance patch behind an environment toggle, following the Proton/GE pattern.** Impact: low. Effort: low. Evidence: toggle list at https://github.com/GloriousEggroll/proton-ge-custom; this repo's own `WINE_DISABLE_GL_PRESENT` precedent (`patches/BASE.txt:167`). +7. **Track Valve Proton experimental, proton-cachyos, and Proton-EM as a standing watch list for Wine 11 sync and Wayland changes, rather than as patch sources.** Impact: low. Effort: low. Evidence: https://github.com/ValveSoftware/Proton/wiki/Changelog; import cadence shown at https://github.com/CachyOS/proton-cachyos/releases. diff --git a/notes/performance-moonshot/docs/OWN-FORK-BUILD-AND-RUNTIME.md b/notes/performance-moonshot/docs/OWN-FORK-BUILD-AND-RUNTIME.md new file mode 100644 index 00000000..e6eac06c --- /dev/null +++ b/notes/performance-moonshot/docs/OWN-FORK-BUILD-AND-RUNTIME.md @@ -0,0 +1,136 @@ +# Own-fork build and runtime review for the performance moonshot + +This document helps the reader decide which build-system and runtime-environment changes are worth making to get xrun-free low-latency audio and a responsive UI out of Ableton Live under this Wine fork. An xrun is an audio buffer overrun or underrun: one missed processing cycle, heard as a click or dropout. + +Scope: `build.sh`, `Makefile`, `Containerfile`, the launcher, setup and diagnostic scripts under `scripts/`, the portal helpers under `bin/`, and the probes under `tools/`. The patch series under `patches/` is out of scope; a sibling document owns it. Where a launcher variable only exists because of a patch, this document covers the variable, not the patch. + +## How the runtime is built today + +The build is a pinned, containerized Wine compile. Facts: + +| Fact | Value | Evidence | +|---|---|---| +| Base image | Ubuntu 22.04, pinned by digest | `Containerfile:10` | +| Unix-side compiler | gcc from jammy `build-essential` (gcc 11.2) | `Containerfile:37-39`; [packages.ubuntu.com/jammy/gcc](https://packages.ubuntu.com/jammy/gcc) | +| PE-side compiler | clang/lld 21, exact package version pinned | `Containerfile:13-17` | +| Configure invocation | `CPPFLAGS=-I/opt/ntsync-uapi` only; no `CFLAGS`, `CROSSCFLAGS`, or `LDFLAGS` overrides | `scripts/container-build.sh:53-56` | +| Effective optimization | `-O2` with debug info on both sides: the vendored base's `configure` defaults `CFLAGS` to `-g -O2` (configure lines 5649/5655) and the PE cross flags to `-g -O2` (line 7627) inside `vendor/wine-base-5c23dd1c.tar.zst` | `scripts/container-build.sh:53-56` (nothing overrides them) | +| PipeASIO compile | gcc `-fPIC -O2 -DNDEBUG -fvisibility=hidden` | `scripts/container-build.sh:150-158` | +| ableton-linkd compile | g++ `-O2`, static libstdc++/libgcc | `tools/build_ableton-linkd.sh:33-38` | +| Architectures | i386 + x86_64 WoW64 | `scripts/container-build.sh:55` | +| Debug info | built with `-g`, then stripped (`llvm-strip --strip-all` on PE, `strip --strip-unneeded` on Unix) | `scripts/container-build.sh:178-186` | +| Build cache | ccache, 5 GB, host directory bind-mounted | `build.sh:31,37`; `Containerfile:90-92` | +| Packaging | zstd `-19 --long=27` tarball | `scripts/container-build.sh:237` | +| Tool probes | clang `-O2`, CRT-free PE builds | `tools/build_metricprobe2.sh:16-24`, `tools/build_mousespy.sh:14-16` | + +`Makefile` is a thin wrapper over `build.sh` and the install scripts (`Makefile:1-28`). `build.sh` verifies vendored checksums, builds the image, runs `container-build.sh`, then builds ableton-linkd (`build.sh:24-44`). + +## Compiler optimization gaps + +The build ships Wine's configure defaults. Nothing raises them. + +- No `-O3` anywhere. Both compiler sides run `-O2`. +- No `-march` / `-mtune` anywhere. The shipped binaries use the generic x86-64 baseline. This is a deliberate property of a relocatable tarball for unknown user machines, not an oversight, but it leaves instruction-set headroom (SSE4.2, AVX2, BMI2) unused. +- No LTO (link-time optimization: cross-module inlining at link time). clang 21 + lld is already the PE toolchain, so ThinLTO is available without new dependencies. Unverified: whether the Wine PE build survives `-flto=thin`; winebuild-generated assembly and `.spec` handling are the usual failure points, so this needs a build plus the existing relocation gate (`scripts/container-build.sh:240-265`) to prove. +- No PGO (profile-guided optimization: recompiling with branch hints from a recorded run). No `-fprofile` flag appears anywhere in the repo. Effort is high because a representative Live workload must be scripted and replayed inside the container. +- No `-fno-plt` and no linker relaxations (`-Wl,-O1`, `-z now`, `-Bsymbolic-functions`). `-fno-plt` removes one indirection on every cross-library call on the Unix side; wineserver, ntdll, win32u, and wined3d make such calls constantly. +- gcc 11.2 compiles the Unix side while clang 21 compiles the PE side. Newer gcc releases improve code generation incrementally. Unverified: whether a newer gcc for jammy (toolchain PPA) keeps the glibc 2.35 runtime floor the tarball promises (`scripts/container-build.sh:230`). Building inside the same jammy image keeps the floor regardless of compiler version. + +## What the launcher sets and does not set + +`scripts/ableton-live` is the canonical runtime environment. Variables it sets: + +| Variable | Value | Purpose | Evidence | +|---|---|---|---| +| `WINEDEBUG` | `-all` | kills fixme spam that stalls Live's UI thread | `scripts/ableton-live:16-17` | +| `WINE_D3D_CONFIG` | `csmt=0x1` | enables wined3d's command-stream thread | `scripts/ableton-live:18` | +| `WINED3D_DCOMP_FORCE_FULL_REDRAW` | `1` | full-surface redraws; correctness over UI throughput | `scripts/ableton-live:19` | +| `WINE_X11_FORCE_OFFSCREEN_CLASS` | `Ableton Live Window Class` | keeps Live on the offscreen path (M4L flicker fix) | `scripts/ableton-live:20-23` | +| `WINE_DISABLE_UNIX_MOUNT_REPARSE` | `1` | browser treats host mounts as plain dirs | `scripts/ableton-live:24-26` | +| `WINEDLLOVERRIDES` | `mscoree,mshtml=` (plus `dcomp=` opt-in) | keeps Mono/HTML-help hooks out | `scripts/ableton-live:29-38` | +| `WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS` | SwiftShader software rendering, `--no-sandbox` | Learn View renders where Wine GPU compositing does not | `scripts/ableton-live:39-42` | +| `WINE_CPU_TOPOLOGY` | capped at 8 CPUs | groundwork only: "Inert on this runtime until the patched ntdll/wineserver consumer lands" | `scripts/ableton-live:75-108` | +| `WINESERVER` | this build's wineserver | binds the prefix to the patched server | `scripts/ableton-live:27` | + +Notable absences and behaviors: + +- `WINEESYNC`/`WINEFSYNC` are explicitly unset by the portal wrappers and setup (`bin/ableton-live-portal:24`, `bin/ableton-wine-portal:15`, `scripts/setup-prefix.sh:56`). ntsync is the synchronization path; nothing to add here. +- No `/dev/ntsync` presence check at launch. On a kernel without it, every NT synchronization wait becomes a wineserver round trip, and the user gets no warning. The build-time comment prices this at "~1.3 cores with Live running" (`scripts/container-build.sh:104-107`); the regression note measured ~45% of one core and ~9,000 context switches per second at idle with the ASIO device open (`notes/ABLETON-WINE-NTSYNC-REGRESSION.md:11-14`). +- No wineserver persistence flag. Each cold launch kills stale servers and runs a synchronous `wineboot` (`scripts/ableton-live:187-196`). That is startup cost, not steady-state audio cost. +- No `taskset`/affinity or `nice` adjustment on the Live process. The launcher-wide RT wrapper is the only scheduling control (next section). +- The WebView2 flags force CPU rendering (SwiftShader) for Live 12's Learn View (`scripts/ableton-live:42`). Deliberate correctness tradeoff; it spends CPU on every visible Learn pane. + +## Realtime scheduling, governor, IRQs, rtkit + +`scripts/setup-realtime.sh` is the host-tuning surface. It writes drop-ins and applies them; it advises on, but never performs, bootloader and kernel changes. + +| Measure | Status | Evidence | +|---|---|---| +| rtprio 95, memlock unlimited, nice -19 for the `audio` group | installed | `scripts/setup-realtime.sh:72-77` | +| `vm.swappiness = 10` | installed + applied | `scripts/setup-realtime.sh:80-88` | +| CPU governor `performance` + systemd unit | installed + applied | `scripts/setup-realtime.sh:90-122` | +| `threadirqs` kernel parameter | advised only | `scripts/setup-realtime.sh:124-133` | +| rtirq (IRQ threading priorities) | enabled only if already installed | `scripts/setup-realtime.sh:134-144` | +| lowlatency / PREEMPT_RT kernel | advised only, for sub-256-frame buffers | `scripts/setup-realtime.sh:146-151` | +| wineserver `chrt -f 95` boost | deliberately omitted (root per launch; priority inversion risk) | `scripts/setup-realtime.sh:23-25` | + +At launch, the launcher probes `chrt -r 10 true` and runs the whole Wine process tree under `SCHED_RR` priority 10 when the probe succeeds (`scripts/ableton-live:780-783`). SCHED_RR is a realtime round-robin policy: GUI threads inherit it along with audio threads. PipeASIO separately requests `SCHED_FIFO` priority 15 for its data-loop thread (`notes/ABLETON-WINE-RT-SCHEDULING.md:3-7`). The same note lists the open risks — Linux throttles realtime tasks to 950 ms per second by default, all inherited threads share one RR priority, and Live's realtime threads outrank the `SCHED_OTHER` wineserver they make synchronous calls to (`notes/ABLETON-WINE-RT-SCHEDULING.md:31-41`) — and prescribes a pending 4-CPU A/B comparison before changing the default (`notes/ABLETON-WINE-RT-SCHEDULING.md:43-82`). That comparison is still unrun; no `bench/` directory or committed results exist in the repo. + +rtkit (a D-Bus service that grants realtime scheduling to unprivileged clients) is not referenced anywhere in the repo. PipeWire uses it for its own data loops on hosts where it is installed; this project neither requires nor verifies it. + +## PipeWire and the audio path + +- PipeASIO is the only shipped ASIO driver; it is a native PipeWire client with no JACK layer (`notes/ABLETON-WINE-PIPEASIO.md:3-5`). +- Prefix setup seeds `~/.config/pipeasio/config.ini`: 2 inputs, 2 outputs, fixed 256-frame buffer, auto-connect (`scripts/setup-prefix.sh:591-603`). `PIPEASIO_*` variables override per launch (`scripts/ableton-live:760-762`). +- PipeWire 1.6 or newer can match the graph quantum (the processing cycle size in frames) to the ASIO buffer; a 256-frame configuration produced `force-quantum` 256 in validation (`notes/ABLETON-WINE-PIPEASIO.md:20,72`). The runtime accepts host PipeWire 0.3.56 or newer (`Containerfile:100-105`), so hosts on 0.3.x–1.5 get no quantum matching and nothing warns them. The README recommends 1.6+ (`README.md:40`); TROUBLESHOOTING repeats the version check (`TROUBLESHOOTING.md:106`). +- No PipeWire host configuration (clock rate, quantum, wireplumber device rules) is seeded or verified by any script. The only quantum evidence path is manual `pw-metadata -n settings` / `pw-top`, referenced by the tester kit (`beta/tester-kit/lib/collect-linux.sh:148-149`) and the audio check's failure hint (`scripts/check-live-audio.sh:57-60`). +- Validation recorded ~8% Live DSP load at 48 kHz / 256 frames, with the PipeWire error counter moving 24→26 on a loaded machine; the note itself flags this as not a controlled latency comparison (`notes/ABLETON-WINE-PIPEASIO.md:83-85`). +- The one measured audio-adjacent CPU sink outside the driver is Live's APC-coalescing thread: 30–40% of one core at idle. ntsync does not accelerate the alertable waits behind it. The attempted `-DontCombineAPCs` workaround caused playback starvation and was reverted; a Wine-side fix is proposed but unimplemented (`notes/ABLETON-WINE-APC-COALESCING.md:3-16`). + +## Kernel requirements + +| Requirement | Needed for | Where checked | Evidence | +|---|---|---|---| +| kernel ≥ 6.14 (`/dev/ntsync`) | ntsync; without it every NT wait is a wineserver round trip | build-time header gate; `check-ntsync.sh` notes the missing device; no launch-time check | `Containerfile:94-98`; `scripts/check-ntsync.sh:38`; `scripts/container-build.sh:104-123` | +| `threadirqs` cmdline | IRQ threading | advised by setup-realtime.sh only | `scripts/setup-realtime.sh:124-133` | +| lowlatency / PREEMPT_RT kernel | sub-256-frame buffers | advised by setup-realtime.sh only | `scripts/setup-realtime.sh:146-151` | +| glibc ≥ 2.35 | runtime floor of the tarball | stated in BUILD-INFO | `scripts/container-build.sh:230` | + +ntsync's measured value: 4–50x more synchronization throughput after it was restored, and wineserver idle load dropped from ~45% of a core (`notes/ABLETON-WINE-NTSYNC-REGRESSION.md:11-14`). + +## Benchmarking and probe coverage + +The harness is `scripts/bench-run.sh`. Its protocol: before/after row pairs under fixed reference conditions — a committed reference set, 48 kHz / 256 frames, fixed window geometry, one machine per comparison (`scripts/bench-run.sh:7-11`). Four metrics: + +| Metric | How captured | Evidence | +|---|---|---| +| `wined3d_cs_pct` | automated: 60 s of per-thread `top` samples | `scripts/bench-run.sh:52-65` | +| `wineserver_ctxt_delta` | automated: 60 s of `/proc` ctxt-switch counters | `scripts/bench-run.sh:67-95` | +| `xruns_5min` | operator-entered from a `pw-top` ERR delta | `scripts/bench-run.sh:34,50` | +| `dsp_load_pct` | operator-entered from Live's DSP meter | `scripts/bench-run.sh:35` | + +Coverage gaps: + +- No automated xrun capture. The headline metric is typed in by a human reading `pw-top`. +- No round-trip latency measurement (loopback), no DSP-load automation, no startup-time metric, no UI-responsiveness metric (frame pacing, input latency), no idle-CPU metric for the APC-coalescing thread — the APC note says to record those "separately unless that script is extended" (`notes/ABLETON-WINE-APC-COALESCING.md:59-63`). +- The "committed reference set" the protocol requires is not in the repo: no `bench/` directory exists. Unverified: testers may hold it privately; either way it is not committed. +- Gates that do exist: `scripts/check-ntsync.sh` (semantics probe + `/dev/ntsync` open check, with wineserver context-switch delta at `scripts/check-ntsync.sh:52-62`), `scripts/check-live-audio.sh` (Live log scan for a clean ASIO open), `scripts/check-m4l-fonts.sh` (font-fallback hang regression), `scripts/build-audit.sh` (per-patch artifact fingerprints). These are correctness gates, not performance gates. +- `tools/` probes are diagnostic, not benchmarks: `mousespy.c` (global mouse-hook routing trace), `linkprobe.c` (Link multicast verdict), `midihot.c` (MIDI hotplug listener), `metricprobe2.c` (DPI metric pinning), `stresstest.c` (session-allocator hammer), `xsamp.c`/`xrec.c` (X-side pixel and protocol spies). None measures latency or throughput under load. + +## Key opportunities + +Ranked by expected impact on xrun-free low-latency audio and UI responsiveness. Planned items are proposals, not verified results. + +1. **Add a launch-time `/dev/ntsync` check with a loud fallback warning.** Impact: high (users on kernel < 6.14 silently lose ~1 core to wineserver round trips). Effort: low (a `[ -c /dev/ntsync ]` test plus message in `scripts/ableton-live`). Evidence: `scripts/container-build.sh:104-107`, `scripts/check-ntsync.sh:38`, `notes/ABLETON-WINE-NTSYNC-REGRESSION.md:11-14`. +2. **Automate the benchmark harness's audio metrics and commit the reference set.** Planned: capture `pw-top -b` ERR deltas and `pw-metadata` rate/quantum directly in `bench-run.sh`, add a startup-time and an idle-CPU column, commit the reference set under `bench/`. Impact: high (every other opportunity here is judged by this harness; today the headline metric is hand-entered and the reference set is absent). Effort: low-medium. Evidence: `scripts/bench-run.sh:34-50`; no `bench/` directory in the repo. +3. **Run the prescribed SCHED_RR A/B and narrow realtime to audio threads.** The launcher puts the whole Wine process under RR 10, GUI threads included; the pending 4-CPU comparison decides whether that helps, needs a CPU-count floor, or should shrink to the PipeASIO data loop's FIFO 15. Impact: high on low-core machines, medium elsewhere. Effort: low (protocol and tooling already written). Evidence: `notes/ABLETON-WINE-RT-SCHEDULING.md:31-82`, `scripts/ableton-live:780-783`. +4. **Raise compiler optimization: `-O3` plus ThinLTO on the clang PE side, `-O3 -fno-plt` on hot Unix halves.** clang 21 + lld is already the PE toolchain; ThinLTO needs no new dependency. Unverified: PE build survival under LTO, and the size of the win; gate on the existing relocation gate plus a bench pair. Impact: medium-high. Effort: medium. Evidence: `scripts/container-build.sh:53-56` (no flag overrides), `Containerfile:13-17`. +5. **Seed and verify PipeWire host configuration.** Planned: at setup or launch, check the PipeWire version (quantum matching needs 1.6+), verify or set the device's clock rate and quantum via metadata/wireplumber rules, and warn when the graph quantum disagrees with the ASIO buffer. Impact: medium-high (a mismatched quantum is a direct xrun source; today nothing checks it). Effort: low-medium. Evidence: `notes/ABLETON-WINE-PIPEASIO.md:20,72`, `TROUBLESHOOTING.md:106`, `scripts/check-live-audio.sh:57-60`. +6. **Ship a `-march=x86-64-v2` build (SSE4.2/PopCNT, 2009-era floor), optionally with a v3 variant.** The tarball currently targets the generic baseline. v2 is near-universal on machines that run Live 12; v3 (AVX2) could be a separate opt-in artifact. Impact: medium. Effort: low (one flag each side, once). Evidence: `scripts/container-build.sh:53-56`; relocatable-tarball policy at `build.sh:15-17`. +7. **Complete the `WINE_CPU_TOPOLOGY` consumer so the 8-CPU cap actually applies.** The launcher computes and exports the cap but marks it inert; on high-core machines Live sizes thread pools from the full CPU count. Impact: medium on >8-core machines. Effort: medium (the consumer is a wineserver/ntdll change — patch territory; the launcher half is done). Evidence: `scripts/ableton-live:75-108`. +8. **Automate IRQ affinity instead of advising it.** Planned: package or vendor an rtirq equivalent, and fail the realtime check when `threadirqs` is missing rather than printing a note once at setup. Impact: medium. Effort: medium (host policy surface). Evidence: `scripts/setup-realtime.sh:124-144`. +9. **Add a kernel check to the realtime report: lowlatency/PREEMPT_RT and `threadirqs` presence, surfaced by the launcher or tester kit.** Sub-256-frame buffers are the project's own stated case for these kernels; today the advice prints once during setup. Impact: medium. Effort: low. Evidence: `scripts/setup-realtime.sh:146-151`, `beta/tester-kit/lib/collect-linux.sh:148-154`. +10. **Upgrade the Unix-side compiler past gcc 11.2 inside the same jammy image.** Keeps the glibc 2.35 floor while picking up several years of code-generation improvements. Unverified: exact gain; measure with a bench pair. Impact: low-medium. Effort: low-medium (one apt pin in `Containerfile`). Evidence: `Containerfile:37-39`, [packages.ubuntu.com/jammy/gcc](https://packages.ubuntu.com/jammy/gcc), floor at `scripts/container-build.sh:230`. +11. **Make wineserver persistent across launches (`wineserver -p`) or skip re-boot when the session is already bound to this build.** Cuts cold-start time and the per-launch `wineboot`; no steady-state audio effect. Impact: low-medium (UI responsiveness at launch). Effort: low. Evidence: `scripts/ableton-live:187-196`. +12. **PGO for the PE build using a scripted Live workload.** Unverified: needs a reproducible Live session to profile, and clang PGO on Wine PE is unexplored here. Impact: medium. Effort: high. Evidence: no `-fprofile` anywhere in the repo (grep over `scripts/`, `tools/`, `Containerfile`, `build.sh`). +13. **Revisit the WebView2 SwiftShader flags once Wine GPU compositing improves.** CPU rendering of every Learn View pane is a standing UI-thread cost, accepted for correctness. Impact: medium (UI). Effort: low to re-test, unknown to fix (depends on the GPU-renderer work tracked elsewhere). Evidence: `scripts/ableton-live:39-42`, `notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md`. diff --git a/notes/performance-moonshot/docs/OWN-FORK-PATCH-MAP.md b/notes/performance-moonshot/docs/OWN-FORK-PATCH-MAP.md new file mode 100644 index 00000000..2ea5dcdf --- /dev/null +++ b/notes/performance-moonshot/docs/OWN-FORK-PATCH-MAP.md @@ -0,0 +1,195 @@ +# Ableton-wine patch map: what the fork already changes + +This document inventories every patch the ableton-wine fork carries, so that later performance and stability work does not duplicate fixes that already exist. It is a map, not an evaluation: it records what each patch does and where it came from, and proposes nothing. + +## Scope and base + +- The series holds 62 top-level patch files, numbered 0001 through 0064. Numbers 0027 and 0044 are intentionally absent (`patches/BASE.txt:14-15`). +- A two-patch sub-series for the PipeASIO audio driver lives in `patches/pipeasio/`. +- The base is `giang17/wine` branch `d2d1-dcomp-11.13` at commit `5c23dd1c`: Wine 11.13 plus a DirectComposition/D2D stack (`patches/BASE.txt:3-5`, https://github.com/giang17/wine). +- Per-patch provenance is recorded in `patches/BASE.txt:32-240`. +- Patch purposes below summarize each patch's own commit message. The Subject line sits at line 1 of each patch file, except patches 0037, 0040, 0041, 0042, 0043, 0045, 0063, and 0064, where it sits at line 4. + +## How to read the tables + +One table per category. Columns: + +- **Patch**: number and short name. The full file is `patches/-.patch`. +- **Purpose**: one line, from the patch's commit message. +- **Relevance**: `performance` (changes CPU use, bandwidth, latency, or throughput), `stability` (prevents a crash, hang, loop, or flicker), or `correctness` (behavioral or visual fidelity only). +- **Origin**: `upstreamable` (a general Wine fix that upstream could take) or `experimental` (fork-specific heuristic, environment-variable switch, Live-specific workaround, or third-party patchset). Provenance cites `patches/BASE.txt`. +- **Note**: the `notes/` document covering the patch, where one exists. `BASE` means only `patches/BASE.txt` records it. + +## Terms used in this document + +- **Non-client (NC) area**: the window zone outside the app's drawing area — title bar, borders, menu band. Live draws its own, which is why so many patches touch it. +- **MWM / Motif hints**: X11 properties that tell the window manager which decorations and buttons a window wants. +- **Frame extents**: the `_NET_FRAME_EXTENTS` X11 property reporting the window manager's frame thickness. +- **Override-redirect**: an X11 window the window manager ignores; Wine uses it for popup menus. +- **DirectComposition (dcomp)**: Microsoft's window-composition API; WebView2 and JUCE plugin editors present through it. +- **Comp buffer / reblit**: the fork keeps a persistent composition buffer per swapchain and re-copies ("reblits") it into the target window on a 200 ms timer. Six patches tune this machinery. +- **Present / swapchain**: a present hands a finished Direct3D frame to the display; the swapchain is the object that owns the frame buffers. `wined3d` is Wine's Direct3D implementation. +- **GDI**: Windows' 2D drawing API; also Wine's fallback present path that copies frames through main memory. +- **DPI awareness context**: the scaling mode a thread uses when reading window metrics. Mismatched contexts misplace frames at fractional display scales. +- **XDG Desktop Portal**: the D-Bus service Linux desktops provide for native file dialogs and file-manager actions. +- **ASIO / PipeASIO**: ASIO is the low-latency audio driver API Live uses; PipeASIO is the PipeWire-native ASIO driver this project ships. +- **PipeWire graph**: PipeWire's processing network. It owns the sample rate and the buffer quantum. + +## Windowing and non-client decorations + +The largest category. Most entries fight window-growth loops, double frames, or decoration mismatches around Live's custom-drawn frame. Patches 0006 through 0013 are a title-bar experiment series and its reverts; `patches/BASE.txt:27-29` requires applying them in sequence. + +| Patch | Purpose | Relevance | Origin | Note | +|---|---|---|---|---| +| 0002 NSPA visible-rect + decoration gates | Moves the window==client check after the style-mask lookup so custom-NC apps still get decoration masking; stops a 4 px/frame growth loop (Wine bug 57955) | stability | Experimental — from nine7nine/wine-nspa-src (`patches/BASE.txt:34-36`) | `notes/ABLETON-WINE-REBASE-11.13.md:21` (bug 57955 open as of 2026-07-17; current status Unverified — bugs.winehq.org was not reachable for re-check) | +| 0003 NSPA 1c frame extents + reentrancy | Frame-extents handling, reentrancy suppression, ncsize guard; fixes the winex11 atom enum order locally | stability | Experimental — nine7nine plus local fix (`patches/BASE.txt:34-36`) | same as 0002 | +| 0004 GNOME per-thread reentrant state | Makes reentrant-WM_WINDOWPOSCHANGED state per-thread (menu wedge race); drops MWM decor for custom-NC windows | stability | Local (`patches/BASE.txt:37-38`); plausibly upstreamable, general reentrancy bug | BASE | +| 0005 no frame allowance for custom-NC | Removes the white rim around Live's modal dialogs | correctness | Local; Live-specific visual fix | BASE | +| 0006 disable frame-extents reconstruction | Stops comdlg32 dialogs landing off-screen at high DPI | stability | Local workaround; later reverted and re-instated by 0008/0009 | `notes/ABLETON-WINE-RESIZE-BUG.md` | +| 0007 clamp top-level size to monitor | Anti-growth clamp for bug 57955; reverted by 0008 | stability | Local experiment, reverted | `notes/ABLETON-WINE-RESIZE-BUG.md` | +| 0008 re-enable frame-extents round-trip | Reverts 0006 and 0007 to converge Live's window feedback via the real WM frame | stability | Local experiment | `notes/ABLETON-WINE-RESIZE-BUG.md` | +| 0009 revert frame-extents re-enable | Undoes 0008: the re-enable reintroduced a strobing white border and did not stop the resize loop | stability | Local revert | `notes/ABLETON-WINE-RESIZE-BUG.md` | +| 0010 captioned tool windows use WM decor | Lets captioned tool windows take WM decorations at high DPI; reverted by 0013 | correctness | Local experiment, reverted | BASE | +| 0011 frame extents for tool windows | Re-enables frame-extents reconstruction for tool windows only; reverted by 0012 | correctness | Local experiment, reverted | BASE | +| 0012 revert of 0011 | Revert | correctness | Revert | BASE | +| 0013 revert of 0010 | Revert | correctness | Revert | BASE | +| 0014 native WM frame for captioned tool windows | Gives plugin editors the native frame and maps the X window to exactly the client area, so the oversized Wine caption is never drawn | correctness | Local; general mechanism, Live-motivated | `notes/ABLETON-WINE-PLUGIN-TITLEBAR-BUG.md` | +| 0015 sync layered attributes on every flush | Forwards color-key/alpha to the scaled surface target each flush; fixes opaque black JUCE shadows | correctness | Local; plausibly upstreamable | `notes/ABLETON-WINE-PLUGIN-TITLEBAR-BUG.md` | +| 0017 real timestamps in _NET_ACTIVE_WINDOW | Sends the last input timestamp; GNOME's mutter drops timestamp-0 activation requests, which wedged menus and focus | stability | Local; flagged upstreamable (`notes/ABLETON-WINE-INPUT-BUG.md:93`) | `notes/ABLETON-WINE-INPUT-BUG.md` | +| 0037 always advertise MWM_FUNC_CLOSE | Keeps Live's close button present while its startup modal disables the main window (KWin) | correctness | Local; upstreamable candidate, general hint behavior | BASE (issue #31) | +| 0039 never flip a mapped window to managed | Refuses the unmanaged-to-managed transition while mapped; stops dropdown unmap/remap flashes and eaten clicks | stability | Local; upstreamable candidate | `notes/ABLETON-WINE-DROPDOWN-MANAGED-FLIP.md` | +| 0053 export minimum tracking size as PMinSize | The WM clamps interactive resizes at Live's minimum, ending the below-minimum drag fight | stability | Local; upstreamable candidate | `notes/ABLETON-WINE-GPU-RENDERER.md` | + +## Audio + +One Wine patch and the two PipeASIO driver patches. No patch touches Wine's audio streaming paths (winepulse, winealsa audio, mmdevapi buffering). + +| Patch | Purpose | Relevance | Origin | Note | +|---|---|---|---|---| +| 0021 mmdevapi FriendlyName re-wrap | Stops wrapping a stored endpoint FriendlyName again on every reload; the multi-level names crashed or hung Live's device enumeration | stability | Local; upstreamable, general bug | `notes/ABLETON-WINE-AUDIO-CRASH-BUG.md` | +| pipeasio 0001 keep graph sample rate | Reports success at the PipeWire graph rate instead of ASE_NoClock; Live treated the refusal as fatal and crash-looped on fresh installs (`patches/pipeasio/0001-asio-keep-graph-sample-rate-instead-of-ASE_NoClock.patch:14-30`) | stability | Experimental — downstream patch to vendored PipeASIO 1.2.2 | `notes/ABLETON-WINE-PIPEASIO.md` | +| pipeasio 0002 timeGetTime systemTime | Reports `timeGetTime()` as the ASIO systemTime so Live stops dropping live-played MIDI as out of window (`patches/pipeasio/0002-asio-report-timeGetTime-in-ASIO-systemTime.patch:17`) | correctness | Experimental — downstream PipeASIO patch | `notes/ABLETON-WINE-PIPEASIO.md` | + +## MIDI and input + +| Patch | Purpose | Relevance | Origin | Note | +|---|---|---|---|---| +| 0028 winealsa MIDI re-subscribe | Re-subscribes open MIDI ports when a device reappears on the ALSA sequencer | stability | Local; upstreamable | `notes/ABLETON-WINE-MIDI-HOTPLUG.md` | +| 0034 flush XdndStatus replies | Flushes each drag-status reply so a fast source's XdndLeave cannot overtake drop acceptance | stability | From ENCORE (`patches/BASE.txt:59-61`); upstreamable, general X fix | `notes/ABLETON-WINE-ENCORE-REVIEW.md` | +| 0038 keep menu tracking on in-process focus | Sends WM_CANCELMODE only when another client's window actually holds X focus; stops dropdowns closing under mutter/muffin focus shuffles | stability | Local; upstreamable candidate | `notes/ABLETON-WINE-MENU-FOCUSOUT.md` | + +## Sync and threading + +Two patches, both about the wineserver shared session mapping (a shared-memory region holding window-class objects). No patch touches wait paths, APC delivery, or thread scheduling. + +| Patch | Purpose | Relevance | Origin | Note | +|---|---|---|---|---| +| 0018 pre-dirty shared session pages | wineserver memsets grown session blocks so clients fault in real pages; fixes a block-boundary match | stability | Local; upstreamable candidate | `notes/ABLETON-WINE-INPUT-BUG.md` | +| 0019 map session views MAP_SHARED | Maps session views read-write/shared so clients keep seeing server writes; a private mapping lost class registrations and crashed window creation | stability | Local; flagged for upstream review (`notes/ABLETON-WINE-INPUT-BUG.md:93-94`) | `notes/ABLETON-WINE-INPUT-BUG.md` | + +## Graphics and GL, including DirectComposition + +The second-largest category. It holds the fork's DirectComposition reblit machinery and its two measured present-path wins (0055, 0059). + +| Patch | Purpose | Relevance | Origin | Note | +|---|---|---|---|---| +| 0001 sashaduke redraw patchset | Implements dcomp/dxgi frame statistics, refresh-rate-aware WaitForVBlank, refresh-rate fallback in swapchain descs, color-space semi-stubs, `WINED3D_DCOMP_FORCE_FULL_REDRAW`, and cs.c assert relaxations | performance + stability | Experimental — third-party patchset from sashaduke/ableton-live12-linux (`patches/BASE.txt:32-33`) | `notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md` | +| 0016 orphaned dcomp target subclass | Keeps the window's true original wndproc; an orphaned subclass swallowed all mouse input on JUCE D2D editors | stability | Local; flagged upstreamable (`notes/ABLETON-WINE-INPUT-BUG.md:93`) | `notes/ABLETON-WINE-INPUT-BUG.md` | +| 0020 sRGB pixel formats on EGL | Advertises and honors sRGB-capable formats; baseview/nih-plug editors aborted Live without one | stability | Local; upstreamable | `notes/ABLETON-WINE-INPUT-BUG.md` | +| 0022 reblit timer stops forcing Present | Timer ticks signal the frame-latency event and refresh from the comp buffer instead of forcing a Present | performance | Local; experimental, fork-specific reblit design | BASE (`patches/BASE.txt:37-38` range; message at `patches/0022-dxgi-stop-forcing-swapchain-presents-from-the-dcomp-.patch:1`) | +| 0025 suspend reblits for abandoned swapchains | Skips comp-buffer blits after 3 s idle; its idle-abandonment was later removed by 0041 | performance | Local; experimental; partly superseded | BASE | +| 0026 report drawable visual | Fills the real visual in set_dc_drawable/ReleaseDC; fixes the BadMatch crash that shrank OpenGL plugin editors to 1x1 on depth-32 windows | stability | Local; upstreamable | `notes/ABLETON-WINE-GL-PLUGIN-EDITOR-CRASH-BUG.md` | +| 0030 no stale-sized comp-buffer blits | Gates both comp-buffer blit sites on the buffer matching the swapchain's current size | correctness | Local; experimental | `notes/ABLETON-WINE-LEARNVIEW-FLICKER.md` (partial fix per `patches/BASE.txt:46-47`) | +| 0035 Intel Battlemage G21 | Adds the Arc B580 device ID so wined3d stops reporting it as "HD Graphics 4000", a string Live 12 blacklists into the GDI fallback | performance | Local; commit message says "Should also be upstreamed to Wine" (`patches/0035-wined3d-add-Intel-Battlemage-G21.patch` body) | BASE (issue #11, `patches/BASE.txt:62-64`) | +| 0036 suspend reblits on null d2d1 device | Backs off and kills the reblit timer when the d2d1 device never came up; the poll spun a full core (`patches/0036-dxgi-suspend-dcomp-reblits-on-a-null-d2d1-device.patch:9`) | performance | Local; experimental | BASE (issue #16, `patches/BASE.txt:65-67`) | +| 0041 dcomp presents visible on WebView2 targets | Normalizes unattributed layered targets, retries stale resize blits on a 120 ms timer, removes 0025's idle abandonment | stability + performance | Local; experimental | `notes/ABLETON-WINE-LEARNVIEW-FLICKER.md`, `notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md` | +| 0055 prefer GL present for top-level windows | Sets WINED3D_SWAPCHAIN_PREFER_GL_PRESENT on top-level swapchains; the GDI path cost ~650 MB/s and over one core, GL costs ~0.4 MB/s and ~20% of a core (`patches/0055-dxgi-prefer-GL-present-for-top-level-swapchain-devic.patch:10-13`) | performance | Local; experimental, `WINE_DISABLE_GL_PRESENT` kill switch | `notes/ABLETON-WINE-GPU-RENDERER.md` | +| 0056 gate parked reblits on visibility | Skips reblits for parked (hidden-ancestor) WebView2 panes; the stale stamps fought Live's own UI | stability + performance | Local; experimental | BASE (issue #57, `patches/BASE.txt:182-184`) | +| 0057 Intel devices Ice Lake to Lunar Lake | Adds post-2019 Intel GPU IDs so Live stops blacklisting them as HD Graphics 4000 | performance | Local; marked for upstreaming (`patches/0057-wined3d-add-Intel-graphics-devices-from-Ice-Lake-to-.patch` body) | `notes/ABLETON-WINE-GPU-RENDERER.md` (issue #84) | +| 0058 GDI present on client-rect disagreement | Routes a frame to the GDI path when the CS-thread client rect disagrees with the captured destination rect; prevents frames landing low with a black band | correctness | Local; experimental safety gate | `notes/ABLETON-WINE-GPU-RENDERER.md` (issue #100, PR 98) | +| 0059 query flip rect in window's DPI context | Brackets the y-flip's client-rect query like 0023; restores the GL path under fractional scaling (98.9% back to 29.8% of a core, `patches/0059-wined3d-query-the-flip-s-client-rect-in-the-window-s.patch:32`) | performance + correctness | Local; upstreamable, same class as 0023 | `notes/ABLETON-WINE-GPU-RENDERER.md` | +| 0061 describe unlisted GPUs from driver string | Synthesizes the GPU description from the driver's own renderer string instead of guessing a 2010-era card Live blacklists | performance | Local; upstreamable, mirrors the Vulkan backend's behavior | `notes/ABLETON-WINE-GPU-RENDERER.md` (issue #84) | + +## DPI and display + +| Patch | Purpose | Relevance | Origin | Note | +|---|---|---|---|---| +| 0023 present/resize rects in window's DPI context | Brackets the present-time client-rect queries with the window's own DPI awareness context | correctness | Local; upstreamable | `notes/ABLETON-WINE-PIANOTEQ-DPI-GHOST-BUG.md:32-35` | +| 0024 present/resize DPI diagnostics at trace | Demotes the PRESENT-DBG/RESIZE-DBG probes from fixme to trace | correctness | Local; fork diagnostics, not for upstream | `notes/ABLETON-WINE-PIANOTEQ-DPI-GHOST-BUG.md:37-38` | +| 0029 menu bar SM_CYMENU + 4 | Lays out the menu band 4 px taller so NCCALCSIZE matches Live's outer-rect model at 96 DPI | correctness | Local; Live-specific geometry matching | `notes/ABLETON-WINE-DPI-SCALE-100.md` (per `patches/BASE.txt:43-45`) | +| 0040 scale menu band with menu DPI | Makes the band max(4, muldiv(4, dpi, 96) − 1); the resize negotiation converges in one pass at 125–200% | stability + correctness | Local; Live-specific geometry matching | `notes/ABLETON-WINE-DPI-SCALE-100.md`, `notes/FINDINGS-RESIZE-GROWTH-2026-07-21.md` | +| 0042 alias sub-scale WM config rounding | Treats sub-scale grant/request differences as compositor rounding instead of feeding them to Win32; stops the 2 px/cycle growth and answers in-band requests locally, removing one request per pointer motion during drags | stability + performance | Adapted from ENCORE (`patches/BASE.txt:86-90`); experimental workaround — an upstream *report* is drafted, not a patch | `notes/FINDINGS-RESIZE-GROWTH-2026-07-21.md`, `notes/ABLETON-WINE-DPI-SCALE-100.md`, `notes/ABLETON-WINE-RESIZE-BUG.md`, `notes/UPSTREAM-ISSUE-DRAFT-RESIZE-PARITY.md`, `notes/ABLETON-WINE-ENCORE-REVIEW.md` | + +## Portals and desktop integration + +| Patch | Purpose | Relevance | Origin | Note | +|---|---|---|---|---| +| 0031 XDG file dialog portal | Adds the portal backend to comdlg32: GetOpenFileName, GetSaveFileName, IFileDialog, SHBrowseForFolder go through native dialogs | correctness | Port of Wine MR !10060 v5 (`patches/BASE.txt:48-51`); the MR remained an unmerged draft as of 2026-07-17 (`notes/ABLETON-WINE-REBASE-11.13.md:19-20`) and Wine's review list still shows it as Draft on 2026-08-01 (https://source.winehq.org/reviews; MR: https://gitlab.winehq.org/wine/wine/-/merge_requests/10060) | `notes/ABLETON-WINE-FILE-PORTAL.md` | +| 0043 reveal /select via OpenURI portal | Routes `explorer.exe /select,""` to `org.freedesktop.portal.OpenURI.OpenDirectory` | correctness | Local; builds on 0031's portal library | `notes/ABLETON-WINE-SHOW-IN-EXPLORER.md` | +| 0063 reveal /select folders via FileManager1 | Routes folder /select targets to `org.freedesktop.FileManager1.ShowItems`; OpenDirectory is file-only | correctness | Local; same basis | `notes/ABLETON-WINE-SHOW-IN-EXPLORER.md` | +| 0064 route folder-open commands to host | Routes `/e,`, `/root,`, and bare-directory explorer commands to `FileManager1.ShowFolders` | correctness | Local; same basis | `notes/ABLETON-WINE-SHOW-IN-EXPLORER.md` | + +## Menus, theming, and fonts + +All five serve the native win32 menu chrome that the launcher themes to match Live. Mechanisms are general win32u fixes. + +| Patch | Purpose | Relevance | Origin | Note | +|---|---|---|---|---| +| 0049 drop grayed-item engraved bevel | Single-draws grayed menu items in COLOR_GRAYTEXT; the two-pass bevel looked wrong on dark themes | correctness | Bug is upstream ("zero diff from stock Wine", `patches/BASE.txt:115-121`); upstreamable | `notes/ABLETON-WINE-MENU-COLOR-THEMING.md` | +| 0050 invalidate sys-color cache on WM_SYSCOLORCHANGE | Re-reads colors and frees cached brushes/pens when another process calls SetSysColors | correctness | Local; upstreamable | `notes/ABLETON-WINE-MENU-COLOR-THEMING.md:139,226`, `notes/FINDINGS-LIVE-THEME-PREVIEW-SIGNAL-2026-07-26.md` | +| 0051 SetSysColors repaints non-client area | Adds RDW_FRAME to the forced repaint so menu bars and captions follow | correctness | Local; upstreamable | `notes/ABLETON-WINE-MENU-COLOR-THEMING.md:151,227` | +| 0052 hide menu-bar mnemonic underlines | Hides the alt-key underlines real Windows only shows after Alt; also makes the dead DT_HIDEPREFIX flag actually work in user32 | correctness | Mixed: the DT_HIDEPREFIX gating fix is upstreamable; hiding the underlines is a style choice (`patches/BASE.txt:138-148`) | `notes/ABLETON-WINE-MENU-COLOR-THEMING.md:196,228` | +| 0054 linked-font fallback for menu glyphs | Falls back to SystemLink families when the Ableton Sans substitute lacks a glyph; measures with the same fallback font | correctness | Local; experimental — whole-string swap trade-off recorded in the commit message | `notes/ABLETON-WINE-MENU-FONT-FALLBACK.md` | + +## Live-specific workarounds + +Patches that exist only because of Live's behavior or hardware. + +| Patch | Purpose | Relevance | Origin | Note | +|---|---|---|---|---| +| 0032 host USB bridge for Push 2 | Exports the 16-function Win64 libusb 1.0.23 ABI so Push2DisplayProcess.exe drives the Push 2 display through host libusb | stability | Experimental — helper-scoped bridge, i386 half disabled (`patches/BASE.txt:52-55`) | `notes/ABLETON-WINE-PUSH2-DISPLAY.md` | +| 0033 WINE_DISABLE_UNIX_MOUNT_REPARSE | Reports Unix mount points as plain directories; Live's browser omitted folders behind unresolvable junctions | correctness | From ENCORE (`patches/BASE.txt:56-58`); experimental environment-variable workaround | `notes/ABLETON-WINE-ENCORE-REVIEW.md` | +| 0062 keep a selected top-level class offscreen | `WINE_X11_FORCE_OFFSCREEN_CLASS` pins Live's main window class on the offscreen path; M4L track selection no longer unmaps/reparents the whole client (black flash) | stability | Local; experimental — environment variable with an exact class name | `notes/ABLETON-WINE-M4L-SELECTION-FLICKER.md` | + +## Misc: shell, OLE, and build maintenance + +| Patch | Purpose | Relevance | Origin | Note | +|---|---|---|---|---| +| 0045 reject foreign-process RevokeDragDrop | Returns DRAGDROP_E_INVALIDHWND instead of dereferencing another process's drop-target pointer; fixes the WebView2 plugin-close crash | stability | Ported from giang17/wine `fafb443f85e0`; not in upstream Wine as of 2026-07-24 (`patches/0045-ole32-reject-RevokeDragDrop-for-windows-owned-by-oth.patch:28`), current status Unverified | `notes/ABLETON-WINE-WEBVIEW2-PLUGIN-CLOSE-CRASH.md` | +| 0046 build fix: frame-latency semaphore | Adapts the reblit timer to 11.13's frame-latency-as-semaphore refactor | correctness | Fork maintenance, not for upstream | `notes/ABLETON-WINE-11.11-TO-11.13-BASE-BUMP.md` | +| 0047 build fix: fractional-DPI ratio | Wraps the menu-band DPI math in round_dpi() after 11.13's struct-ratio refactor | correctness | Fork maintenance | `notes/ABLETON-WINE-11.11-TO-11.13-BASE-BUMP.md` | +| 0048 build fix: libusb detection | Checks the cache variable 11.13's AC_CHECK_FUNC actually sets, so the Push 2 bridge builds again | correctness | Fork maintenance | `notes/ABLETON-WINE-11.11-TO-11.13-BASE-BUMP.md` | +| 0060 implement IFileOperation DeleteItem | Implements Live's shared deletion path through the copy engine; Wine's method was an E_NOTIMPL stub | correctness | Local; upstreamable, real implementation of a stub | BASE (`patches/BASE.txt:210-214`, PR 108) | + +## Performance measures outside the patch series + +These systems already cover performance ground without being Wine patches. Later work must not duplicate them. + +| System | What it does | Evidence | +|---|---|---| +| ntsync (kernel fast path for NT synchronization) | The build vendors `linux/ntsync.h` and fails if either runtime half is missing. Without it every NT wait crosses wineserver: ~45% of one core idle and ~9,000 context switches/s; restoring it gave 4–50× synchronization throughput | `notes/ABLETON-WINE-NTSYNC-REGRESSION.md:3-14`, `vendor/ntsync-uapi/` | +| Real-time scheduling | The launcher starts Wine under `SCHED_RR` priority 10 when permitted; PipeASIO requests `SCHED_FIFO` 15 for its data-loop thread | `notes/ABLETON-WINE-RT-SCHEDULING.md:3-5,16-17` | +| Live GPU renderer enablement | `setup-prefix.sh` removes `-_ForceGdiBackend` so Live uses its Direct2D/D3D11 renderer; idle CPU dropped from ~59% of one core to 1–2% | `notes/ABLETON-WINE-GPU-RENDERER.md:8-9,28-29` | +| PipeASIO driver | Native PipeWire ASIO client; replaced WineASIO and removed JACK from Live's audio path in release 2026.07.17.2 | `notes/ABLETON-WINE-PIPEASIO.md:3-4` | +| Ableton Link | Live joins Link through Wine's unmodified network stack; a native daemon (`tools/ableton-linkd.cpp`) holds the session across restarts | `notes/ABLETON-WINE-LINK-FIRSTCLASS.md:11`, `notes/ABLETON-WINE-LINK.md` | +| APC coalescing fix | Proposal only. Unimplemented: the `-DontCombineAPCs` experiment was reverted and current releases strip it | `notes/ABLETON-WINE-APC-COALESCING.md:9,77-79` | +| Tempo ramp timing drift | Investigation open, no fix; ruled out ntsync, scheduling, QPC, and pipeasio 0002 | `notes/FINDINGS-TEMPO-RAMP-2026-07-31.md:3,38-41` | + +## Coverage summary + +Patch counts per category (64 total: 62 top-level + 2 pipeasio): windowing/NC 18, graphics/GL 16, DPI/display 5, menus/theming 5, portals 4, misc 5, audio 3, MIDI/input 3, Live-specific 3, sync/threading 2. + +**Heavily invested areas:** + +- **Window geometry at fractional scale.** 23 patches across windowing and DPI fight growth loops, double frames, and resize feedback (Wine bug 57955 lineage plus the 0040/0042 menu-band and rounding work). This is the fork's deepest investment, and it is converged-behavior work, not throughput work. +- **The DirectComposition and present path.** 16 graphics patches, including the fork's own reblit timer design (0022, 0025, 0030, 0036, 0041, 0056) and the two measured CPU/bandwidth wins (0055, 0059). GPU identification (0035, 0057, 0061) exists to keep Live off its GDI fallback. +- **Desktop fit and finish.** Portals (4), menu theming and fonts (5), and explorer integration are well covered and mostly correctness-class. + +**Lightly touched or untouched areas:** + +- **Sync and threading.** Only 0018/0019, both stability fixes for session shared memory. No patch touches wineserver wait paths, alertable waits, APC delivery, or thread priorities. The APC coalescing fix is an unimplemented proposal (`notes/ABLETON-WINE-APC-COALESCING.md:77`). ntsync coverage is build-level, not a patch (`notes/ABLETON-WINE-NTSYNC-REGRESSION.md`). +- **Audio streaming.** One Wine patch (0021, endpoint naming) plus two PipeASIO driver patches. Wine's audio engines (winepulse, winealsa audio, mmdevapi buffering and event delivery) carry no fork patches. +- **MIDI.** One hotplug patch (0028). MIDI timestamping is covered only inside PipeASIO (pipeasio 0002). +- **Live 11.** The wmvcore media-playback crash is documented with no patch (`notes/ABLETON-WINE-LIVE11-WMVCORE-STUB.md:3`). +- **Startup cost, registry, fonts at load, memory use, wineserver protocol volume, and networking.** No patch targets these. Networking is deliberately unpatched (`notes/ABLETON-WINE-LINK-FIRSTCLASS.md:11`). +- **The known tempo-ramp timing divergence** has no fix and no assigned subsystem (`notes/FINDINGS-TEMPO-RAMP-2026-07-31.md:3`). diff --git a/notes/performance-moonshot/docs/PERFORMANCE-MOONSHOT-2026-08-01.md b/notes/performance-moonshot/docs/PERFORMANCE-MOONSHOT-2026-08-01.md new file mode 100644 index 00000000..870e6fc2 --- /dev/null +++ b/notes/performance-moonshot/docs/PERFORMANCE-MOONSHOT-2026-08-01.md @@ -0,0 +1,754 @@ +# Performance moonshot research, 2026-08-01 + +This document records the research pass behind the `performance-moonshot` +branch. It describes how Ableton Live behaves as a Windows application, what +Proton and other Wine forks do for speed and stability, where our own stack +stands, and which improvements we should try, in ranked order. It records +research only. Nothing in this document is implemented. + +## How to read this document + +Sections 1 to 4 are the four reviews. Section 5 is the combined opportunity +list. Every opportunity names its evidence, its cost, and the measurement that +proves or disproves it. A performance claim needs a before/after pair from +`scripts/bench-run.sh` under the reference conditions in that script's header. + +## The stack today + +Facts about the current runtime, for calibration. File references point at the +source of each fact. + +- The Wine base is 11.13, from `giang17/wine` branch `d2d1-dcomp-11.13` at + commit `5c23dd1c` (`patches/BASE.txt`). That branch carries a Direct2D and + DirectComposition rendering stack that Live 12's interface needs. +- 62 Wine patches apply on top (`patches/`). Most fix window management, + redraw, display scaling, dialogs, and GPU identification. They are + correctness patches, not speed patches. +- Audio runs through PipeASIO 1.2.2, an ASIO driver that talks to PipeWire + directly (`vendor/pipeasio-1.2.2.tar.gz`, two patches in + `patches/pipeasio/`). ASIO is the Windows low-latency audio driver + interface that Live prefers. The PipeWire floor is 0.3.56. +- NT synchronization uses ntsync, the Linux kernel driver that implements + Windows wait primitives. The build fails if either the wineserver half or + the ntdll half is missing (`scripts/container-build.sh`). Without ntsync, + every wait becomes a round trip to wineserver, Wine's central coordination + process, which cost about 1.3 CPU cores with Live running + (`scripts/container-build.sh`, step 3 comments). +- The launcher (`scripts/ableton-live`) disables Wine debug logging, keeps the + multithreaded Direct3D command stream on, forces full DirectComposition + redraws, pins Live's main window class to an offscreen rendering path, and + runs the whole process under round-robin realtime scheduling at priority 10 + when the system grants realtime rights. +- The main window presents frames through OpenGL directly from the GPU + (patch 0055). Before that patch, every frame crossed to main memory and + back, about 650 MB per second (issue 91). A per-frame gate falls back to + the old path when window geometry disagrees mid-frame (patches 0058, 0059). +- Live's embedded browser views (WebView2) render in software via SwiftShader, + set by launcher flags. This is a stability choice with a known speed cost. +- The host profile (`scripts/setup-realtime.sh`) grants realtime priority 95, + unlimited locked memory, and nice -19 to the audio group, sets swappiness + to 10, and installs a performance CPU governor unit. It advises threaded + interrupts and a realtime kernel but changes neither. +- The launcher exports `WINE_CPU_TOPOLOGY` (capped at 8 CPUs), but the + launcher comment records that nothing in this Wine base consumes it yet. +- The build uses Wine's stock compiler optimization level. No -O3, no + link-time optimization, no profile-guided optimization, no CPU baseline + above generic x86-64 (`scripts/container-build.sh`). Builds are + containerized, input-pinned, stripped, and audited per patch + (`scripts/build-audit.sh`). +- Measurement exists: `scripts/bench-run.sh` records the Direct3D command + stream thread's CPU share, wineserver context switches per minute, audio + dropouts per 5 minutes, and Live's DSP load into `bench/results.csv`. + +## 1. How Ableton Live works + +Facts about Live as a Windows application, from Ableton documentation, +release notes, and credible measurements. Full source URLs are in the +Sources section. + +### Engine and threading + +- Live parallelizes across independent signal paths only. Everything along + one signal path runs serially, and the longest path bounds capacity on one + core. Wake-up latency of a few hot threads therefore matters more than + total throughput. +- Live's audio workers are Windows realtime threads registered through MMCSS, + the Windows service that boosts audio threads. Windows caps them at 32 per + process; Live 12.4 adds an option (`EnableRealTimeWorkQueue`) that raises + the pool to 64 threads through the Real-Time Work Queue API. The manual + states that Live expects the audio thread to hold the highest priority. + Wine implements MMCSS as a stub, so under Wine this entire priority + structure silently disappears. +- Since Live 11.3, Live restricts audio processing to performance cores on + hybrid CPUs, and it decides based on the reported Windows version: + `-RestrictAudioCalculationToPerformanceCores` defaults to true when Live + sees Windows 11. What Windows version and CPU topology our prefix reports + therefore changes Live's own thread placement. +- The CPU meter reports buffer processing time divided by buffer duration, a + deadline ratio, not an operating-system CPU percentage. It is + frequency-scaling sensitive, and it works as a free fidelity benchmark + against Windows on identical hardware. +- Effects with no incoming audio suspend automatically. Unused audio + interface channels cost constant CPU, and Live never disables them at run + time because drivers misbehave on configuration changes. + +### The ASIO contract + +- Live does more than exchange buffers with an ASIO driver: it sets and + validates the sample rate, queries clock sources, opens the driver's own + control panel from the Hardware Setup button, and derives recording + offsets from the latencies the driver reports. Wrong reported latency + shifts every recording; the Driver Error Compensation setting exists to + paper over exactly that. +- Hidden options (`-AsioNoSetSampleRate`, `-AsioDisableMultiClient`, and + others) toggle parts of this contract per install. + +### Rendering + +- Live's default Windows rendering is software rasterization with one final + Direct3D composite and scale step. Live 12.2 added an opt-in GPU renderer, + and Ableton's own release notes fixed interface stalls above 100 ms and + audio glitches attributed to that renderer. `-_ForceGdiBackend` forces a + pure CPU path. Renderer choice is a first-class stability knob even on + Windows. +- The stack under the composite step is Direct3D 11 with DirectComposition, + which is why this fork's base branch exists. Ableton requires whole or + half display scaling steps (100, 150, 200 percent) and no per-application + DPI override. + +### Embedded browser views + +- WebView2 (the Edge-based embedded browser) is load-bearing in current + Live: the Learn View (12.4) is a WebView with a separate + picture-in-picture window, and Splice integration (12.3) refuses to run + without the WebView2 runtime. WebView2 initialization fails outright on a + DPI-awareness mismatch between host window and runtime. + +### Timers, MIDI, disk + +- Live requests 1 ms timer resolution and schedules outgoing MIDI itself + because Windows MME MIDI carries no timestamps. Timer granularity and + monotonic clock quality directly set MIDI jitter. +- Samples stream from disk by default under a per-buffer deadline; the Disk + Overload indicator reports missed deadlines. Live writes a continuous + undo and crash-recovery journal instead of timed autosaves, and the + browser is a SQLite database with documented disk-error corruption modes. + File-system semantics in Wine (sync behavior, locking, memory mapping) + sit under all three paths. +- The plugin scanner skips NTFS reparse points, so symbolic links to plugin + folders are invisible to it under Wine; hard links work. + +### Portability boundary + +- Push 3 standalone runs Live's engine as a native Linux binary on an Intel + Core i3 with a PREEMPT_RT kernel and ALSA. The DSP core is proven + portable; the Windows-bound surface is the desktop shell: Direct3D and + DirectComposition UI, WebView2, plugin hosting, MME MIDI, MMCSS + scheduling. Every dropout under Wine is attributable to platform layers we + control. +- Live 12 refuses to start without AVX2 CPU instructions, so AVX2 is a + guaranteed baseline on every Live 12 machine. Live 11 has no such floor. +- Ableton's own performance guidance (performance power plans, no frequency + scaling, USB selective suspend off, antivirus exclusions) matches what our + host realtime profile already automates or advises. + +## 2. What Proton does for speed and stability + +Proton is Valve's Wine distribution for games. The review checked each Proton +mechanism against our actual base (verified by extracting and searching the +vendored Wine 11.13 source) and against Valve's current trees. + +### Already in our base, inherited from upstream Wine + +Valve and CodeWeavers upstreamed most of their performance work, and Wine +11.13 contains it. We ship all of the following today: + +- ntsync (called "inproc sync" upstream). This is the end state of the + esync, fsync, ntsync lineage. Against plain wineserver synchronization it + measures 50 to 150 percent faster in many programs; against fsync it is + roughly equal in throughput and better in correctness and frame-time + consistency. +- Shared-memory session state. Cursor position, key state, queue status, and + window class lookups read shared memory instead of calling wineserver. + Polling loops in a user interface thread are already round-trip-free. Most + "wineserver makes Wine slow" folklore predates this work. +- The heap rewrite with the Low Fragmentation Heap frontend (Wine 8.3). +- Write-watch tracking via userfaultfd (Wine 10.7). +- FAudio, the low-latency winepulse rework, and winegstreamer. +- Windows thread priorities map to Linux nice values (Wine 11.0), but only + when the process may lower niceness, and the realtime priority band is + explicitly clamped away. The source carries a FIXME at the clamp. +- winedmo, an FFmpeg-based media decoding alternative to winegstreamer, + ships in our base dormant behind a registry value. + +### Proton-only, the actionable gap list + +- Per-thread realtime scheduling for the Windows realtime priority band: + implemented by nobody, upstream or Proton. The wineserver FIXME above is + an open opportunity, and it matters most for a DAW, where the audio + threads must outrank the interface threads. Our whole-process `SCHED_RR` + wrapper currently flattens exactly that differentiation. +- MMCSS (the Windows service that boosts audio threads, used through + avrt.dll) is a stub in upstream and in Proton. Live's "Pro Audio" thread + registrations do nothing everywhere. No Wine tree integrates RTKit. +- `WINE_CPU_TOPOLOGY` has its consumer only in Proton's tree + (`fill_cpu_override`, about 200 lines, prefers physical and performance + cores). Our launcher export does nothing on our base: port the patch or + delete the export. +- fsync remains in Proton as the fallback for kernels older than 6.14 + without ntsync. Our fork has no such tier; those users fall to full + wineserver synchronization. Proton also carries an ntsync kill switch + (`PROTON_NO_NTSYNC`); we have no equivalent A/B switch. +- Heap crash-triage switches: Proton exposes `WINE_HEAP_DELAY_FREE` and + heap zeroing as environment gates over machinery our base already + contains. Useful against use-after-free bugs in third-party plugins. A + few-line port. +- DXVK and vkd3d-proton translate Direct3D to Vulkan. Direct2D runs on the + public Direct3D 11 API, so Live-style interface workloads ride DXVK by + default under Proton, which makes DXVK a credible, heavily field-tested + A/B candidate against our wined3d OpenGL present path. DirectComposition + is the exception: Proton has nothing there, and our giang17 base is ahead + of Proton on that axis. +- Prefix lifecycle management: Proton stamps prefixes with a version file, + tracks every file it installs, and replays migrations on upgrade. Our + launcher repairs prefix drift ad hoc. The pattern is cheap to adopt. +- The knob discipline itself: every risky Proton mechanism has a documented + one-line off switch honored per run. That is what makes field debugging + tractable, and our `ABLETON_*`/`PIPEASIO_*` variables already follow it in + part. + +### Checked and not applicable + +- Timer resolution (`timeBeginPeriod`): a stub in every tree, and harmless + on Linux because waits use high-resolution timers anyway. +- Large page support: implemented nowhere; transparent hugepages are the + only effect in play. +- Fossilize shader pre-caching and the Steam media converter: bound to + Steam infrastructure, and a Direct2D interface compiles too few pipelines + to need caching. +- Steam Linux Runtime containers: our pinned container build and vendored + inputs already serve the same purpose at build time. + +## 3. Where our own stack can improve + +Findings from the audit of this repository. File references point at the +evidence. + +### Baselines already measured + +- Live idles with one Wine thread coalescing asynchronous procedure calls + (APCs, the mechanism Windows programs use to run a callback on another + thread) at 30 to 40 percent of one core + (`notes/ABLETON-WINE-APC-COALESCING.md`). The `-DontCombineAPCs` option + removed the cost but broke playback (issue 29) and was reverted. +- Without ntsync, idle Live at buffer size 256 drives wineserver to about 45 + percent of a core and 9,000 context switches per second. Synchronization + probes run 4 to 50 times faster with ntsync + (`notes/ABLETON-WINE-NTSYNC-REGRESSION.md`). +- The retired CPU present path copied about 650 MB per second to the display + server and held more than one core while the mouse moved. The OpenGL + present path idles at 1 to 2 percent (`notes/ABLETON-WINE-GPU-RENDERER.md`, + issue 91). With gate patches 0058 and 0059 both applied, presenting costs + about 25 percent of one core at 125 percent scale with the mouse moving. +- PipeASIO at 48 kHz and buffer size 256 measures about 8 percent DSP load in + Live (`notes/ABLETON-WINE-PIPEASIO.md`). +- The bench harness (`scripts/bench-run.sh`) has produced zero committed + rows. `bench/results.csv` does not exist in history. Every claim below + names the measurement that would decide it. + +### Audio path + +- PipeASIO forces its configured buffer size onto the PipeWire graph + (`PW_KEY_NODE_FORCE_QUANTUM`, PipeASIO `src/audio.c`). PipeWire arbitrates + competing forced sizes by recency. When another client wins, the driver + warns once and keeps feeding Live its own buffer size each graph cycle, + which plays audio at the wrong speed. This is the issue 49 mechanism. +- Configuration validation silently replaces any buffer size that is not a + power of two between 16 and 8192 with 1024 (`src/asio.c`). The circulated + workaround value 192 lands at 1024. +- The driver's realtime thread setup calls `pthread_setschedparam` directly + with no RTKit fallback. RTKit is the desktop service that grants realtime + priority to unprivileged processes. On stock Fedora and Ubuntu the audio + thread silently runs without realtime scheduling. +- Live's entire audio processing runs synchronously inside the PipeWire data + loop cycle. The copy cost is one memory copy per direction per channel per + cycle, which is negligible. +- The driver reports a fixed one-buffer latency estimate to Live and never + invokes the latency-changed callback, so Live's driver latency compensation + never learns the real device and resampler delay. +- A quantum-follow mode (`follow_device_clock`) already contains the + renegotiation machinery a general fix needs. It is gated to that mode. +- The seeded duplex configuration resolves the default input and output + devices independently. Two different devices means two sample clocks in + one graph and periodic resync crackle. + +### Rendering and window machinery + +- Every DirectComposition target keeps a 200 ms reblit timer for the whole + session (patches 0036 and 0041). Patch 0056 gates the blit on visibility; + the timer keeps ticking. An open embedded-browser pane receives a full-pane + copy at 5 Hz with content that hashed identical in 30 of 30 samples. +- Patch 0055 excludes `WS_POPUP` windows from the OpenGL present path because + they show black until the first interaction. Popups and gated frames keep + the full-frame CPU copy path. +- The launcher forces full DirectComposition redraws + (`WINED3D_DCOMP_FORCE_FULL_REDRAW=1`). The flag predates the GPU renderer + and suppresses dirty-rectangle presents wherever the CPU path still runs. +- Two host helpers poll: `learnheal.exe` walks the full window tree once per + second for the whole session, and the theme watcher wakes every 2 seconds + and scans the process list. + +### Scheduling + +- wineserver maps Windows thread priorities to niceness only, and only when + the host profile's nice -19 grant exists. A Windows realtime priority never + becomes Linux realtime scheduling; the source carries a FIXME for exactly + this (`server/thread.c` in the Wine base). +- The launcher's whole-process `SCHED_RR 10` is therefore the only realtime + scheduling Live's audio worker threads ever receive. PipeASIO's data loop + runs `SCHED_FIFO 15` and waits on Live workers that may hold no realtime + priority at all on stock distributions: a priority inversion. +- `WINE_CPU_TOPOLOGY` is inert. The variable appears nowhere in the vendored + Wine base or the patch series. + +### Synchronization residue with ntsync active + +- Queueing an APC to another thread is a wineserver round trip, and an + alertable sleep waits in the server (`dlls/ntdll/unix/thread.c`, + `unix/sync.c` in the Wine base). Live runs an APC loop near 1 kHz. This is + the measured idle cost above and the reason `bench-run.sh` records + wineserver context switches. +- Message waits and handle lifecycle also remain server-side. + +### Build + +- Effective compiler flags are Wine's defaults: `-g -O2 -fno-strict-aliasing` + on the Unix side, `-g -O2` on the PE side. No CPU baseline raise, no + link-time optimization, no profile-guided optimization. +- The build audit fingerprints shipped binaries per patch + (`scripts/build-audit.sh`). A post-link optimization step would add + unpinned inputs to that chain. + +### Unmerged work and open issues + +- Branch `fixes/audio-hardening` holds the corrected issue 49 analysis + (`notes/ABLETON-WINE-PIPEASIO-CRACKLE.md` on that branch) and an ordered + fix plan, F0 through F8, with a risk table and a three-distribution + verification matrix. It is the most valuable unmerged artifact in the + repository. +- Branch `fixes/options-txt-perf` holds the known-bad `-DontCombineAPCs` + experiment. Keep it as history. +- Open performance and stability issues: 49 (crackle), 109 (ntsync waits + return instantly; an installer spun at 18,700 waits per second), 63 (VST + interaction delay, no measurements yet), 115 (Convolution Reverb freeze on + device open), 87 (Splice pane stops receiving input after collapse and + reopen; 28,730 versus 710 mouse messages measured), 92 (black screen on + F11), 42 (fullscreen), 111 (installer hang while the USB driver runs), 46 + (MIDI hotplug). + +## 4. What other Wine forks do + +Survey of the fork landscape as of 2026-08-01, ordered by relevance to this +project. + +### wine-nspa, the closest prior art + +wine-nspa (nine7nine) is a pro-audio Wine for native Windows DAWs, alive on a +Wine 11.8 base, and it benchmarks its changes on Ableton Live 12 workloads, +so its measurements transfer to our application directly. Its design +invariant: with its realtime variable unset, every code path is byte +identical to upstream Wine. + +We took two windowing commits from it (our patches 0002 and 0003) and nothing +else. The unadopted remainder, with wine-nspa's own measurements on Live: + +- Per-thread realtime layering: Windows time-critical threads and audio + threads get realtime classes at distinct priorities, plugin worker threads + included, instead of one flat process priority. +- MMCSS mapped to real scheduling: thread boosts derive from the + application's own AvSetMmThreadCharacteristics calls. +- Priority inheritance on locks (priority inheritance means a lock lends its + waiter's priority to the current holder, which prevents a low-priority + thread from stalling a realtime one): critical sections ride + `FUTEX_LOCK_PI` on a stock kernel, condition variables use requeue-PI, and + worst-case condition wait latency fell from 263 to 152 microseconds. +- Memory locking under a realtime gate: `mlockall` with on-fault locking cut + maximum futex wait from 94 to 49 microseconds and page faults by 20 + percent on Live playback. Huge-page promotion follows on top. +- Hot-path work: thread-local state moved into the thread environment block + cut CPU cycles 14.3 percent on a 30 second Live playback; an X11 flush + throttle plus a vectorized surface copy cut winex11 CPU 64 percent; heap + commit hysteresis and cache-line isolation round it out. +- Message rings: shared-memory rings for same-process Win32 messages cut + message round trips by 78 to 99 percent on Live. One caution transfers: + their session-wide shared-memory variant caused a Live library-panel + regression, fixed by per-queue memory; our own shared-session patches + (0018, 0019) sit near the same ground. +- io_uring file paths, a client-side scheduler thread, and local timer and + event objects that avoid wineserver. +- A custom-kernel overlay (priority-ordered ntsync waits, mutex-owner + priority boosting, kernel channel objects that replaced its wineserver + fast path, 95 percent dispatcher CPU cut). This tier conflicts with our + every-Linux-computer target and only fits as an optional future overlay. + +The stock-kernel tier is portable to our base with medium effort; the fork +force-pushes and its numbers are whole-stack rather than per patch, so each +port must be measured on our side. + +### The wine-osu lineage + +wine-osu (whrvt/wine-osu-patches, Wine 11.12 plus staging, one minor version +from our base) maintains the low-latency audio patch line. Directly relevant +pieces: + +- An avrt patch that maps AvSetMmThreadCharacteristics("Pro Audio") to + time-critical thread priority: the missing top half of the scheduling + chain in section 5's first opportunity. +- Removal of the 10 millisecond period floor in mmdevapi plus period and + buffer environment overrides. Our engine path bypasses this (ASIO), but + every shared-mode Windows audio consumer inside the prefix gains from it. +- A current rebase (by Paul Gofman) of fsync as an automatic fallback behind + ntsync for kernels without `/dev/ntsync`, with pick order ntsync, fsync, + server. This is the exact missing tier from section 2. +- A working-in-progress winepipewire.drv (39 patches, originally by the + PipeASIO author) giving Wine a native PipeWire audio backend. The same + driver ships enabled by default in proton-cachyos since July 2026. +- Evidence for the timing investigation: upstream Wine answers + QueryPerformanceCounter through a system-call clock path, while Windows + stamps time from the TSC, the CPU's built-in timestamp counter; a + community proton_QPC patch exists. This is a concrete mechanism candidate + for the 0.08 percent tempo-ramp delta and for MIDI jitter. + +### GE-Proton and wine-tkg + +- GE-Proton applies wine-staging minus roughly 30 exclusions with written + rationale per exclusion: a vetted map of known-bad staging patches worth + keeping. Its video rework routes all media through FFmpeg and winedmo with + winegstreamer removed, a maintained fallback architecture if winegstreamer + fails us. One GE patch preserves runtime OpenGL GPU descriptions and + overlaps our patches 0057 and 0061; worth a diff. A winepulse fix recovers + from timestamp wrap in sessions past a few hours. +- wine-tkg is an options matrix, not a patch source: ntsync toggles, a + wineserver realtime capability option, and prefix hygiene switches that + stop Wine from registering file associations system-wide. Its compiler + defaults stay at -O2 with no LTO, PGO, or march raise on offer. + +### wine-staging today + +esync left staging when ntsync went upstream; no scheduling or timer +patchsets remain. The few sets relevant to a GUI plus audio application: +`server-Signal_Thread` (a thread-termination race), PeekMessage and message +order correctness, and window style attribute sync. One warning transfers: +Kron4ek's build config excludes staging's DirectComposition patch as broken, +so staging dcomp must never mix into our d2d1-dcomp base. + +### Build-optimization distributions + +- CachyOS builds Wine at -O2 with AVX explicitly disabled, ships a separate + x86-64-v3 variant, and wires no profile-guided optimization for Wine. + Kron4ek builds at -O3 with a generic x86-64 baseline: the one precedent + for portable -O3. +- No surveyed fork uses profile-guided optimization or post-link + optimization for Wine. That work would be novel, not adoption. +- proton-cachyos ships the winepipewire driver enabled by default and + documents Proton's nice-based thread priority model, the weakest form of + the differentiation section 5 targets. + +### App-specific forks and our own upstream + +- ElementalWarrior's Affinity fork dissolved itself upstream: WineHQ merged + the general work and the app now runs on stock Wine plus two drop-in + files. The pattern to copy: keep patches single-topic, push what + generalizes upstream, ship the pinned tarball meanwhile. +- giang17, our base: the `d2d1-dcomp-11.13` branch was force-refreshed into + a single snapshot commit after we vendored it, so our exact base commit no + longer exists upstream as history (our vendored tarball is now the + provenance record). A `d2d1-dcomp-11.14` branch exists as of 2026-07-31 + with upstream Wine 11.14 underneath, including a winegstreamer stride fix + and a winewayland deadlock fix. That branch is the natural next base bump. + +### Adjacent projects + +- yabridge (plugin bridging): preallocated shared-memory audio buffers with + sockets doubling as wakeups so the realtime path takes no locks, and a + two-sided watchdog that unblocks stuck calls when either side dies. Its + Wine variant delegates thread priority to Wine and keeps priority + inheritance mutexes off the callback body. This is the blueprint for both + PipeASIO hardening and any future Linux-plugin bridge. +- PipeASIO upstream released 1.2.3 (we ship 1.2.2): build fixes only, + including a link fix for distributions that inject link-time optimization + flags. Low-cost vendor bump. +- JACK-era lore that still applies: a realtime watchdog with eviction rather + than hope, dropout accounting as a first-class counter, and the priority + ladder device interrupts above audio server above clients. PipeWire's data + threads default to realtime priority 88 to sit above threaded interrupts + at 50; on a PREEMPT_RT kernel every interrupt becomes a thread at 50, so + that ladder needs a verification run on a realtime kernel before we advise + users about one. + +## 5. Opportunities + +Ranked by expected impact over effort. Each entry states the change, the +evidence, where the work lands, and the measurement that decides it. Entries +marked Speed, Stability, or Both. A claim counts as proven only with a +before/after pair from `scripts/bench-run.sh` under its reference conditions. + +### 0. Record baselines first (prerequisite, Both) + +Change: run and commit `bench/results.csv` rows for the current release, +idle and reference-set playback, before any change lands. Extend the harness +with three columns: Live process total CPU, the APC-coalescing thread's CPU, +and Live's own CPU meter reading. Live's meter is a deadline ratio (section +1), so the same Set on the same hardware under Windows gives a direct +fidelity target. +Evidence: the harness exists with zero committed rows (section 3). +Lands: bench/, `scripts/bench-run.sh`. +Verify: rows exist; every later entry cites them. + +### 1. Restore Live's thread-priority structure (Both) + +The centerpiece. Live registers its audio workers as realtime threads +through MMCSS and expects them to outrank everything else (section 1). Wine +stubs MMCSS, wineserver clamps the realtime band to nice values (section 2), +and our whole-process `SCHED_RR 10` gives the interface the same priority as +the audio path (section 3). Nobody in the ecosystem ships the fix; wine-nspa +proves the model on Live itself (section 4). + +Change, in order: +1. De-stub avrt so AvSetMmThreadCharacteristics("Pro Audio") yields + time-critical priority. Base: the wine-osu avrt patch. +2. Implement the `server/thread.c` FIXME: map the Windows realtime band to + per-thread `SCHED_FIFO`/`SCHED_RR`, budgeted under the host rtprio grant, + placed below PipeASIO's data loop (FIFO 15) and PipeWire (88). Gate + behind an environment switch for A/B runs. +3. Only then retire whole-process `SCHED_RR` as the default. It is currently + the only realtime scheduling Live's workers get; removing it first would + regress every stock machine (section 3 ordering constraint). +4. Add the RTKit fallback in PipeASIO last: RTKit imposes a runtime budget + that would kill the process while interface threads still run realtime. +Lands: wine patch (avrt, server), launcher, pipeasio patch. +Verify: `ps -eLo pid,tid,cls,rtprio,comm` shows realtime only on threads +that asked; bench pairs on a high-core and a 4-core machine; dropout count +at buffer sizes 128 and 64; DSP-load delta on the reference Set. + +### 2. Same-process APC fast path (Speed) + +Change: deliver same-process user APCs through the ntsync alert event +instead of a wineserver round trip, draining before server APCs; +cross-process APCs keep the server path. +Evidence: Live runs an APC loop near 1 kHz; the coalescing thread burns 30 +to 40 percent of a core at idle; the design sketch exists in +`notes/ABLETON-WINE-APC-COALESCING.md` (section 3). The `-DontCombineAPCs` +experiment proved the cost is real and the application-side shortcut breaks +playback, so the fix belongs in Wine. +Lands: wine patch (ntdll). +Verify: wineserver context-switch delta collapses; the coalescing thread +drops under 5 percent idle; playback and automation stay correct on the +reference Set; a dedicated APC-order probe in the tester kit passes. + +### 3. Finish the audio-hardening plan (Both) + +Change: execute the ordered plan already written on branch +`fixes/audio-hardening`: capture-and-diagnose tooling first, then quantum +convergence (follow the graph cycle size instead of forcing our own, accept +sizes that are not powers of two, stop silently replacing invalid +configuration values with 1024), then correct latency reporting with the +latency-changed notification, then a single-clock duplex default. Add +priority-inheritance mutexes on driver state shared with the realtime +thread, a stall watchdog that silences rather than hopes (the JACK eviction +model, section 4), and the PipeASIO 1.2.3 vendor bump. +Evidence: issue 49 remains open; the mechanism is confirmed (section 3); +Live derives recording offsets from reported latency (section 1), so the +static one-buffer guess shifts recordings today. +Lands: pipeasio patches, launcher, `scripts/setup-prefix.sh`, docs. Every +new patch needs its build-audit fingerprint entry. +Verify: the plan's own three-distribution matrix; loopback tests at pinned +graph sizes 192, 384, 1024 with a second forcing client; wall-clock equals +sample count; recording alignment against a loopback cable. + +### 4. Finish the present path (Speed) + +Change: stop the perpetual 200 ms reblit timers when panes are hidden and +re-arm on show; make visible-pane reblits event-driven instead of a 5 Hz +tick; lift the popup-window exclusion from the OpenGL present path by fixing +the first-map black frame; port wine-nspa's X11 flush throttle and +vectorized surface copy for every window still on the CPU path; A/B the +forced full-redraw launcher default. +Evidence: open panes take identical-content full-pane copies at 5 Hz; +popups still pay the copy path that cost about one core before patch 0055 +(section 3); the flush throttle cut winex11 CPU 64 percent on Live playback +(section 4). +Risk: the steady reblit keeps embedded-browser panes composited (patch 0041 +rationale). Change only with the damage-counter and frame-hash evidence at +100, 125, and 200 percent scale. +Lands: wine patches (dxgi, winex11). +Verify: pane damage rate drops to near zero at idle; Learn View, the +documentation sidebar, and Splice survive open, close, and reopen; bench +pairs while dragging inside dialogs. + +### 5. Sync coverage and trust (Stability) + +Change: root-cause issue 109 (ntsync waits returning instantly in a spin) +and add a livelock regression case to the ntsync probe; port the maintained +fsync fallback so kernels older than 6.14 get futex-based sync instead of +full wineserver round trips; add an ntsync off-switch environment variable +for A/B runs, mirroring Proton. +Evidence: section 3 (issue 109, 18,700 waits per second), section 2 (the +missing tier and missing switch), section 4 (the fallback rebase exists and +is current). +Lands: wine patches (server, ntdll), probe suite, TROUBLESHOOTING.md. +Verify: the new probe case fails on an affected kernel and passes after the +fix; the stuck installer completes; sync on/off bench pairs on one machine. + +### 6. Memory and locality (Speed) + +Change: gate `mlockall` with on-fault locking behind the existing realtime +switch; port the thread-environment-block hot-state patch; add the two heap +environment switches (delayed free, zeroed free) as crash-triage tools for +plugin bugs. Huge-page experiments follow later, not first. +Evidence: measured on Live by wine-nspa: maximum futex wait halved, 14.3 +percent cycle cut (section 4); the heap switches are a few-line port over +machinery our base already contains (section 2). Locked memory is already +provisioned by the host profile. +Lands: wine patches (ntdll), launcher, docs. +Verify: bench pairs plus perf counters for page faults and futex wait +times; the heap switches documented and off by default. + +### 7. Core topology and placement (Speed on hybrid CPUs) + +Change: port Proton's `WINE_CPU_TOPOLOGY` consumer (it prefers physical and +performance cores) or delete our inert export; verify which Windows version +the prefix reports, because Live restricts audio to performance cores only +when it sees Windows 11 (section 1); then choose and document a deliberate +policy for hybrid CPUs. +Evidence: the export provably does nothing today (section 3); the consumer +is about 200 self-contained lines (section 2). +Lands: wine patch (ntdll), launcher, docs. +Verify: Live's worker placement on a hybrid CPU before and after; dropout +pairs on a performance-plus-efficiency-core machine. + +### 8. Timing fidelity (Both) + +Change: review the community QPC patch and the timestamp-counter evidence, +then decide whether our QueryPerformanceCounter should read the CPU's +timestamp counter; run +the deferred tempo-ramp export comparison (buffer 64 versus 2048, and 192 +once entry 3 makes it legal); measure MIDI output jitter, since Live +schedules MIDI itself against the 1 ms timer (section 1). +Evidence: upstream answers QPC through a system-call path where Windows +uses TSC (section 4); the 0.08 percent ramp delta remains unexplained +(section 3). +Lands: notes first; wine patch only if the evidence says so. +Verify: ramp-duration comparison against the Windows reference; MIDI +loopback jitter distribution before and after. + +### 9. Prefix lifecycle engine (Stability) + +Change: adopt Proton's prefix pattern: a version stamp in the prefix, a +manifest of every file our tooling installs, and replayable migrations on +update, replacing ad hoc repair passes. +Evidence: section 2; our launcher already repairs handler entries and prefix +drift case by case (section 3). +Lands: launcher and installer scripts. +Verify: update from the previous release replays cleanly on a copy of a +real prefix; uninstall removes exactly the manifest. + +### 10. Strategic tracks (park until the above land) + +- winepipewire.drv port: native PipeWire for every non-ASIO audio path in + the prefix, from the PipeASIO author, shipping default-on in + proton-cachyos. Supersedes the shared-mode floor patches when mature. +- DXVK as an opt-in A/B for Live's Direct3D 11 interface compositing: + heavily field-tested under Proton for exactly this workload class, but it + forfeits our tuned OpenGL present path, so it must win pairs to earn a + default. +- Base bump to giang17 `d2d1-dcomp-11.14`: upstream is snapshot-rebuilt, so + plan a rebase narrative like the 11.11 to 11.13 bump. Brings a + winegstreamer stride fix and a winewayland deadlock fix. +- wine-nspa message rings and local objects: the largest remaining + wineserver cuts with Live-measured numbers, medium-high port effort, and + a known Live library-panel regression in one variant near our own + shared-session patches. Measure carefully. +- A Linux-plugin bridge on the yabridge model (shared-memory audio, no + locks on the realtime path, two-sided watchdog) once the engine-path work + is stable. + +### Small stability fixes, do opportunistically + +- Land the parked cross-process visible-region fix (patch 0046 on + `fixes/issue-57-crossproc-visrgn`), then retest the Splice input-dead + pane (issue 87): same machinery family. +- Port staging `server-Signal_Thread` (thread-termination race; plugin + robustness). +- Port GE-Proton's winepulse timestamp-wrap recovery for sessions past a + few hours. +- Check our fractional-scaling DPI override against WebView2's refusal to + initialize on DPI-awareness mismatches (section 1). +- Diff GE-Proton's GPU-description patch against our 0057 and 0061. +- Document: hard links, never symbolic links, for plugin folders (Live's + scanner skips reparse points); the `/dev/ntsync` requirement and the + global forced-quantum symptom in TROUBLESHOOTING.md. + +### Deliberate non-changes + +- Compiler flags stay at Wine's defaults. No fork ships profile-guided or + post-link optimization for Wine; every hotspot found in this research is + algorithmic, not code generation. A raised CPU baseline would break Live + 11 machines and the one-tarball distribution model. Live 12 guarantees + AVX2 on its hosts, so hand-vectorized routines with runtime dispatch + (the wine-nspa approach) are the acceptable form of vectorization. +- Timer-resolution calls stay stubs: Linux waits are already + high-resolution, so there is no coarse quantum to defeat. +- No staging DirectComposition patches into this base, ever (known broken + with the d2d1-dcomp stack). +- No runtime library container: the pinned build container and vendored + inputs already cover what Steam's runtime solves. +- Whole-process realtime stays the default until entry 1 step 3's evidence + exists. + +## Sources + +Repository evidence: `patches/BASE.txt`, `scripts/container-build.sh`, +`scripts/ableton-live`, `scripts/setup-realtime.sh`, `scripts/bench-run.sh`, +`scripts/build-audit.sh`, `notes/ABLETON-WINE-APC-COALESCING.md`, +`notes/ABLETON-WINE-NTSYNC-REGRESSION.md`, `notes/ABLETON-WINE-GPU-RENDERER.md`, +`notes/ABLETON-WINE-PIPEASIO.md`, `notes/ABLETON-WINE-RT-SCHEDULING.md`, +`notes/FINDINGS-TEMPO-RAMP-2026-07-31.md`, branch `fixes/audio-hardening` +(`notes/ABLETON-WINE-PIPEASIO-CRACKLE.md`), the vendored Wine source, the +PipeASIO 1.2.2 source, and the open issue tracker. + +Ableton: multi-core FAQ (help.ableton.com article 209067649), CPU usage +monitoring (209069609), CPU meter (360019151379), latency articles +(360010545559, 209072409, 209072249), ASIO buffer and sample-rate handling +(209770985), Windows optimization (209071469, 209071269), graphics settings +(4405388230674), Options.txt (6003224107292), video (209773125), disk +overload (115001041970), crash recovery (115001878844), browser database +(360000794970), system requirements (115001663530, 12971338677148), Live 12 +release notes (ableton.com/en/release-notes/live-12), Live 12 manual chapter +37, MIDI fact sheet. Renderer measurements: camplaix.github.io. Windows +realtime-thread cap: helpcenter.steinberg.de article 115000535804. Push 3 +teardown: mslinn.com/av_studio/ableton-p3s-linux.html. Options catalogue: +studiocode.dev/kb/Ableton/ableton-options. + +Proton and upstream Wine: github.com/ValveSoftware/Proton (script, +changelog wiki), github.com/ValveSoftware/wine branch bleeding-edge, Wine +GitLab merge requests 1628 (heap), 3103, 5896, 8976, 8061, 9000 (shared +session state), Phoronix reports on Wine 8.3, 10.7, 10.16, ntsync in Linux +6.14, and Fossilize; fedoraproject.org/wiki/Changes/NTSYNC. + +Forks and adjacent: github.com/nine7nine/Wine-NSPA and wine-nspa-src with +the document set at nine7nine.github.io/Wine-NSPA (client scheduler, cs-pi, +condvar-pi, gamma channel dispatcher, message rings, io_uring, memory and +large pages, hot paths, audio stack, yabridge-nspa, ntsync-pi driver, +current state); github.com/whrvt/wine-osu-patches (branch winello); +github.com/NelloKudo/osu-winello; github.com/GloriousEggroll/proton-ge-custom +(protonprep script, GE-Proton11-1 notes); github.com/Frogging-Family/wine-tkg-git +and community-patches (proton_QPC); github.com/wine-staging/wine-staging; +github.com/CachyOS/CachyOS-PKGBUILDS (wine-cachyos) and +github.com/CachyOS/proton-cachyos; github.com/Kron4ek/Wine-Builds; +github.com/giang17/wine (branches d2d1-dcomp-11.13, d2d1-dcomp-11.14); +github.com/robbert-vdh/yabridge (architecture document); +github.com/wineasio/wineasio; github.com/M0n7y5/pipeasio (release 1.2.3); +docs.pipewire.org (module-rt); blog.thepoon.fr/osuLinuxAudioLatency; +affinity.liz.pet and codeberg.org/wanesty/affinity-wine-docs +(ElementalWarrior outcome). diff --git a/notes/performance-moonshot/docs/PERFORMANCE-MOONSHOT-RESEARCH.md b/notes/performance-moonshot/docs/PERFORMANCE-MOONSHOT-RESEARCH.md new file mode 100644 index 00000000..3a9ba4cc --- /dev/null +++ b/notes/performance-moonshot/docs/PERFORMANCE-MOONSHOT-RESEARCH.md @@ -0,0 +1,299 @@ +# Performance Moonshot: research and opportunity map + +This document records what determines speed and stability for Ableton Live +under this Wine fork, and lists the highest-leverage changes to try. It is a +research input, not a plan of record. Each opportunity carries a confidence +level and a first verification step. Date: 2026-08-01. + +## Scope and method + +Four investigations ran in parallel: + +1. The technologies this Wine fork uses, read from the repository. +2. How Ableton Live works internally, read from Ableton's public documentation. +3. Valve's Proton performance techniques, read from Proton and kernel sources. +4. Other Wine forks and Linux audio work, read from their repositories. + +Findings that need a trace or a build before they become claims are marked +**unverified**. Findings confirmed by a note or a patch in this repository are +marked **confirmed**. Web sources appear inline. + +## Technologies this fork uses + +The fork builds from `giang17/wine` branch `d2d1-dcomp-11.13`, commit +`5c23dd1c` (see [`patches/BASE.txt`](../patches/BASE.txt)). The build runs in a +pinned Podman container on Ubuntu 22.04, with Clang and LLD 21 building the PE +(Windows) side and GCC building the Unix side. Every input is pinned by digest +and checksum. + +| Area | Component | Notes | +|---|---|---| +| Sync primitives | ntsync | Kernel driver via `/dev/ntsync`; header vendored. `WINEESYNC` and `WINEFSYNC` are unset. **confirmed** | +| Graphics | wined3d, OpenGL | `WINE_D3D_CONFIG=csmt=0x1` enables command-stream multithreading. DXVK and VKD3D are not used. **confirmed** | +| Graphics | Direct2D + DirectComposition | The `d2d1-dcomp` base stack drives Live's GPU renderer and WebView2 compositing. **confirmed** | +| Present path | Direct GL present | Patches 0055, 0058, 0059 route top-level swapchains through `glXSwapBuffers`, skipping a frame copy. **confirmed** | +| Audio | PipeASIO 1.2.2 | Native PipeWire ASIO driver. Replaces WineASIO. Links `libpipewire-0.3.so.0` at runtime. **confirmed** | +| Audio fallback | winealsa, winepulse | ALSA carries MIDI; pulse is Wine's own driver. **confirmed** | +| Audio scheduling | SCHED_RR 10 process, SCHED_FIFO 15 PipeASIO | Launcher starts the whole process under round-robin priority 10. **confirmed** | +| Media | GStreamer | mp3 and video import. Host plugins decode at runtime. **confirmed** | +| CPU reporting | WINE_CPU_TOPOLOGY | Launcher caps the reported CPU count at eight, honouring the host cpuset. **confirmed** | +| Hardware | libusb bridge | x86-64 host libusb shim for the Push 2 display. **confirmed** | +| Networking | Ableton Link 4.0 | Vendored daemon joins as a native peer; Wine multicast passes through. **confirmed** | +| Tracing | `WINEDEBUG=-all` | Default in the launcher. **confirmed** | + +## How Ableton Live works + +Ableton does not publish Live's internals. The statements below come from the +Live 11 and 12 release notes and help articles. Anything Ableton does not state +is marked **unverified**. + +### Threading + +Live 12 can create up to 32 real-time audio threads on Windows, and the +`-EnableRealTimeWorkQueue` flag raises that to 64. The scheduling mechanism +(MMCSS "Pro Audio" via the AVRT API) is not documented. **unverified** +Source: https://www.ableton.com/en/release-notes/live-12/ + +### Audio + +Live offers ASIO, MME, DirectSound, and Direct Capture drivers on Windows. ASIO +is the recommended path and is a hard requirement for Ableton Link on Windows. +WASAPI is not offered as a driver type. Buffer sizes are powers of two; 44.1 and +48 kHz are recommended. Source: https://help.ableton.com/hc/en-us/articles/360003149240 + +### Graphics + +Live 12.2 added a Windows GPU renderer (Settings > Display & Input). Live +synchronises with the Desktop Window Manager. The exact API set (Direct2D 1.1, +Direct3D 11, DirectWrite, DirectComposition) is not named by Ableton; the +behaviour observed in this fork matches Direct2D on Direct3D 11 with +DirectComposition. **unverified** Source: https://www.ableton.com/en/release-notes/live-12/ + +### WebView2 + +A Microsoft Edge WebView hosts the Learn View and Splice panels on Windows. +Source: https://www.ableton.com/en/release-notes/live-12/ + +### Plugin hosting + +Live hosts VST2 and VST3 on Windows. Whether hosting is in-process or +out-of-process is not documented. **unverified** JUCE-based VST3 editors with +OpenGL are confirmed working under this fork +([ABLETON-WINE-GL-PLUGIN-EDITOR-CRASH-BUG.md](ABLETON-WINE-GL-PLUGIN-EDITOR-CRASH-BUG.md)). + +### Push 2 hardware + +Push 2 is a USB 2.0 composite device: one MIDI port and one bulk interface for +the 960x160 RGB display. The display is driven through libusb. Source: +https://github.com/Ableton/push-interface + +### Media formats + +Live 12 decodes WAV, AIFF, FLAC, Ogg Vorbis, and MP3 natively. MP3 decoding +moved in-tree for Live 12 (Live 11 used a Windows ACM codec). Video import uses +Windows Media Foundation in Live 12; Live 11 used DirectShow with the Haali +splitter. GStreamer is not used by Live itself. Source: +https://help.ableton.com/hc/en-us/articles/211427589 + +### Hardware requirements + +Live 12 requires an AVX2-capable CPU, Windows 10 22H2 or Windows 11, and 8 GB +RAM. Source: https://help.ableton.com/hc/en-us/articles/115001663530 + +### Linux stance + +Ableton lists Windows and macOS only. There is no official Linux support and no +stated port plan. **confirmed** + +### The `-DontCombineAPCs` flag needs verification + +Ableton's documented definition of `-DontCombineAPCs` is about Akai APC +hardware controllers ("won't align and sync the session rings of multiple +APCs"), not Windows asynchronous procedure calls. Source: +https://help.ableton.com/hc/en-us/articles/6003224107292 + +The note [ABLETON-WINE-APC-COALESCING.md](ABLETON-WINE-APC-COALESCING.md) +records a measured idle CPU thread and a playback regression when the flag is +removed. Those measurements are real, but the flag's documented purpose does not +match the mechanism the note assumes. The first step before any APC work is to +trace an idle session with `WINEDEBUG=+server` and confirm what the busy thread +actually does. **unverified** + +## Proton techniques and DAW applicability + +Valve's Proton optimises games. The table lists each technique, its gain, its +requirement, and whether it applies to a realtime audio application. + +| Technique | What it does | Requirement | Applies to a DAW | +|---|---|---|---| +| ntsync | Handles NT sync waits in the kernel, removing wineserver round trips. Merged mainline in Linux 6.14. 4-50x sync throughput over the fallback in this fork's own measurement. | Linux >= 6.14, `CONFIG_NTSYNC` | Yes. Already used. | +| fsync, esync | Predecessors of ntsync. fsync uses `FUTEX_WAIT_MULTIPLE`; esync uses eventfd. | Staging or Proton Wine | Only on kernels below 6.14. This fork unsets both in favour of ntsync. | +| Modern wow64 | Runs the 32-bit `ntdll.dll` inside the 64-bit process. Fewer syscalls and context switches for 32-bit code. | Wine 9.0+ | Yes, for 32-bit plugin hosting. | +| DXVK | Translates Direct3D 9, 10, 11 to Vulkan. Steadier rendering than OpenGL on modern GPUs. | Vulkan driver | Candidate. See the opportunity below. | +| VKD3D-Proton | Translates Direct3D 12 to Vulkan. | Vulkan driver | Low. Live does not use Direct3D 12. | +| CPU pinning and cap | Reports a chosen CPU count and topology to the app, skipping SMT siblings. | `WINE_CPU_TOPOLOGY` | Yes. Already capped at eight. | +| Large address aware | Gives the process more than 2 GB of address space. | Flag at launch | Yes. | +| `WINEDEBUG=-all` | Removes logging overhead. | Env var | Yes. Already set. | +| Feral GameMode | CPU governor, GPU performance mode, I/O priority. | Daemon | Partial. GPU mode is irrelevant; governor is host-side. | +| Gamescope | Micro-compositor with direct DRM flips. | Compositor | Low. A windowed DAW needs the desktop. | +| LatencyFleX | Frame-input latency sync. | Driver | No. Designed for games and adds microstutter. | + +Proton ships no audio tuning. Steam Deck audio runs on a standard PipeWire +stack. Realtime audio behaviour comes from the host, not from Proton. + +Sources: https://www.kernel.org/doc/html/latest/userspace-api/ntsync.html , +https://github.com/ValveSoftware/Proton/blob/proton_9.0/README.md , +https://github.com/ValveSoftware/wine/blob/experimental_9.0/dlls/ntdll/unix/system.c , +https://github.com/doitsujin/dxvk + +## Other Wine forks and borrowable work + +| Fork or project | Distinctive change | Borrowable here | Conflict | +|---|---|---|---| +| GE-Proton | Media Foundation patches, ntsync enablement, raw input. | Media Foundation for video and WMA import. | FShack fullscreen breaks windowed DAW work and plugin GUIs. | +| wine-staging | `ntdll-APC_Performance`, `server-PeekMessage`, `server-Signal_Thread`, `ntdll-WRITECOPY`, `mfplat-streaming-support`. | The ntdll and server patches; mfplat for media. | Must rebase against the d2d1-dcomp base. | +| giang17/wine `d2d1-dcomp` | Direct2D 1.1 and DirectComposition enablement. | Already the base. | None. | +| wine-tkg-git | Configurable Wine with fsync and FUTEX2. | fsync on pre-6.14 kernels. | Default fshack breaks plugin GUIs. | +| WineASIO | ASIO to JACK bridge. | Pattern. PipeASIO already replaces it. | None. | +| yabridge | Out-of-process VST2, VST3, and CLAP bridge for native Linux hosts. | Realtime setup checklist: `chrt`, preempt kernel, rtirq, performance governor. | Applies to native hosts, not to Live itself. | +| CodeWeavers CrossOver | New wow64, PE DLL conversions, Direct2D and DComp fixes. | Upstream into Wine over time. | None. | + +The native PipeWire Wine audio backend is not merged upstream as of Wine 9 to +10. This fork uses the PipeASIO ASIO shim, which is the working substitute. + +PREEMPT_RT merged into Linux mainline in 6.12 (November 2024). A `preempt=full` +kernel no longer needs a separate patchset. Source: https://www.kernel.org/ + +Sources: https://github.com/GloriousEggroll/proton-ge-custom , +https://github.com/wine-staging/wine-staging , +https://github.com/wineasio/wineasio , +https://github.com/robbert-vdh/yabridge , +https://gitlab.freedesktop.org/pipewire/pipewire + +## Opportunities in this fork + +Each opportunity names the area, the change, the expected effect, a confidence +level, and a first step. + +### Evaluate DXVK for Live's Direct2D and Direct3D 11 UI + +Live's GPU renderer runs through wined3d on OpenGL. DXVK translates Direct3D +11 to Vulkan and, on modern GPU drivers, holds a steadier frame and uses less +CPU than the OpenGL path. Confidence: **unverified**. The d2d1-dcomp base is +written against Wine's own d3d11 and d2d1, so DXVK may regress DirectComposition +and WebView2 compositing. First step: build a one-off prefix with DXVK's +`d3d11.dll`, `dxgi.dll`, and `d3d10core.dll` overriding the built-ins, then run +the existing GL-present benchmark against the reference set. Track regressions +in the Learn View, Splice view, and plugin editors. + +### Confirm the busy idle thread and fix its real cause + +The idle CPU thread documented in +[ABLETON-WINE-APC-COALESCING.md](ABLETON-WINE-APC-COALESCING.md) uses 30 to 40 +percent of one core. ntsync does not accelerate alertable waits or APC delivery, +so those still cross wineserver. Confidence: **unverified cause**. First step: +trace an idle session at 256 frames with `WINEDEBUG=+server`, then count +`select` and `queue_apc` calls on the busy thread. Match the trace to the flag +Ableton actually documents before writing a patch. + +### Backport staging ntdll and server patches + +The `ntdll-APC_Performance`, `server-PeekMessage`, and +`server-Signal_Thread` staging patches cut wineserver calls and message-loop +latency. Confidence: **unverified against this base**. First step: port each +patch onto `d2d1-dcomp-11.13` in isolation, run the tester kit, and record a +`bench-run.sh` pair. + +### Require ntsync at runtime and warn when the kernel lacks it + +The build requires the header, but a user on a kernel below 6.14, or one without +`CONFIG_NTSYNC`, silently falls back to wineserver round trips. This fork's own +probe measured 45 percent wineserver CPU and 9000 context switches per second on +the fallback. Confidence: **confirmed regression**. First step: extend the +launcher to detect `/dev/ntsync` and print a one-line warning when it is absent, +pointing at the kernel requirement. + +### Measure and narrow the realtime policy + +The launcher starts the whole process under `SCHED_RR` priority 10. The note +[ABLETON-WINE-RT-SCHEDULING.md](ABLETON-WINE-RT-SCHEDULING.md) lists three +untested risks on low-core systems, including Live threads outranking the +`SCHED_OTHER` wineserver they call synchronously. Confidence: **unverified**. +First step: run the documented four-core comparison with `bench-run.sh`, then +decide whether to narrow RR to the audio and render threads only. + +### Pin audio and render threads to disjoint cores + +PipeASIO takes `SCHED_FIFO` 15 on its data-loop thread. Live spawns up to 32 +real-time audio threads. Pinning the audio threads to a fixed CPU set, separate +from the GUI and wineserver threads, reduces cache contention and scheduling +jitter. Confidence: **unverified**. First step: confirm `taskset` and +`WINE_CPU_TOPOLOGY` interaction, then run a pinned and an unpinned pair on a +machine with eight or more cores. + +### Bring DXVK-style shader precompilation to plugin editors + +JUCE and OpenGL plugin editors stutter on first open because wined3d compiles +shaders on demand. DXVK uses `VK_EXT_graphics_pipeline_library` to precompile. +Confidence: **unverified**. Couples to the DXVK evaluation above. First step: +confirm the stutter with a frame-time capture on a known JUCE plugin, then +measure under DXVK. + +### Add Media Foundation support for Live 12 video and WMA + +Live 12 imports video through Media Foundation. The GE-Proton and staging +`mfplat-streaming-support` patches cover this path. Confidence: **unverified**. +First step: list which Media Foundation entry points Live 12 hits during an MP4 +import, then check coverage against the GE-Proton patch set. + +### Expand GPU device identification beyond Intel + +Patches 0035, 0057, and 0061 add Intel and AMD device names so Live enables its +GPU renderer. The Vulkan backend already names devices from the driver. Applying +that pattern to the OpenGL backend everywhere (not just as a fallback) removes +per-device table entries. Confidence: **unverified**. First step: audit the +wined3d device table against `pci.ids` and confirm 0061 covers the gaps. + +### Verify the `-DontCombineAPCs` definition before any APC work + +See the verification note above. Ableton documents this flag for Akai APC +controllers, not for Windows APCs. Confidence: **unverified**. First step: ask +Ableton support, or test the flag against an APC controller and an idle session +in the same prefix, and separate the two effects. + +## Ranked opportunities + +Ranking is by expected effect on audio stability and speed, divided by risk and +effort. Each row should become its own change with a `bench-run.sh` pair. + +| Rank | Opportunity | Expected effect | Risk | Effort | +|---|---|---|---|---| +| 1 | Require ntsync at runtime and warn on its absence | Stops a silent 4-50x sync regression | Low | Low | +| 2 | Confirm the busy idle thread and fix its real cause | Removes 30-40% idle CPU | Medium | Medium | +| 3 | Backport staging ntdll and server patches | Cuts wineserver and message-loop latency | Medium | Medium | +| 4 | Measure and narrow the realtime policy | Removes low-core audio risk | Low | Medium | +| 5 | Pin audio and render threads to disjoint cores | Lower jitter under load | Low | Medium | +| 6 | Evaluate DXVK for the Direct2D and Direct3D 11 UI | Steadier render, lower render CPU | High | Medium | +| 7 | Bring shader precompilation to plugin editors | Removes first-open stutter | High | High | +| 8 | Add Media Foundation for Live 12 video and WMA | Restores video and WMA import | Medium | Medium | +| 9 | Expand GPU device identification | Broader GPU renderer coverage | Low | Low | +| 10 | Verify the `-DontCombineAPCs` definition | Corrects the basis for APC work | Low | Low | + +## Open questions + +- Whether DXVK composites correctly with DirectComposition and WebView2 on this + base, or whether it regresses the present path that patches 0055, 0058, and + 0059 already optimise. +- Whether the busy idle thread is an APC loop, a message loop, or something + else. The trace decides the fix. +- Whether whole-process `SCHED_RR` 10 helps or harms on a four-core machine. +- Whether the staging ntdll and server patches rebase cleanly onto + `d2d1-dcomp-11.13` without conflicting with the 62 local patches. +- Which Media Foundation entry points Live 12 actually calls. + +## Verification standard + +No opportunity becomes a claim without a before-and-after pair recorded by +`scripts/bench-run.sh` under the reference conditions stated in that script: +the committed reference set, 48 kHz at 256 frames, fixed window geometry, one +machine per comparison. The unit of evidence is the pair. diff --git a/notes/performance-moonshot/docs/PROJECT-TECHNOLOGIES.md b/notes/performance-moonshot/docs/PROJECT-TECHNOLOGIES.md new file mode 100644 index 00000000..cc074abf --- /dev/null +++ b/notes/performance-moonshot/docs/PROJECT-TECHNOLOGIES.md @@ -0,0 +1,399 @@ +# Project technologies inventory + +This document helps a performance-moonshot planner see every technology the +ableton-linux stack is built from, where each is pinned or defined, and why it +matters for speed or stability. Scope: the repository at branch +`performance-moonshot`, VERSION `2026.08.01.1` (VERSION:1). One section per +technology area. Terms are defined at first use. + +## Wine base + +Wine is a compatibility layer that runs Windows programs on Linux by +implementing the Windows APIs. This project ships a patched Wine fork named +`wine-d2d1-nspa-11.13` (scripts/container-build.sh:11). + +- Upstream source: `https://github.com/giang17/wine.git`, branch + `d2d1-dcomp-11.13`, commit `5c23dd1c` (patches/BASE.txt:3-5; + README.md:273-276). The branch tracks Wine 11.13; the project rebased from + the `d2d1-dcomp-11.11` branch on 2026-07-21 (patches/BASE.txt:7-12). +- `d2d1-dcomp` means the fork carries Direct2D (2D graphics API) and + DirectComposition (window compositing API) work beyond mainline Wine. Live + 12's UI relies on this stack (scripts/ableton-live:33-35). +- `nspa` comes from nine7nine's wine-nspa-src tree, source of patches 0002 and + 0003 (patches/BASE.txt:34-35). +- The source is not fetched at build time. It is vendored as + `vendor/wine-base-5c23dd1c.tar.zst` and verified against + `vendor/wine-base.sha256` before every build (build.sh:25; + scripts/container-build.sh:21-23). Rebuilds cannot drift. + +Why it matters: the base pins the DirectComposition renderer, the present +path, and windowing behaviour that Live's frame rate and UI stability depend +on. Patch 0055 alone moved the main window from a ~650 MB/s GDI copy path to +direct GL presents (patches/BASE.txt:167-178). + +## Patch series and application mechanism + +The series holds 62 Wine patch files, numbered 0001-0064 with 0027 and 0044 +intentionally absent (patches/BASE.txt:14-15). A two-patch sub-series for +PipeASIO lives in `patches/pipeasio/` (patches/SERIES.sha256:63-64). + +Application happens inside the build container +(scripts/container-build.sh:25-47): + +1. Unpack the vendored base tarball (scripts/container-build.sh:23). +2. `git init`, commit the base, then `git am --3way` each `patches/00*.patch` + in lexical order (scripts/container-build.sh:37-45; patches/BASE.txt:19-25). +3. Patch files without mail headers get a fixed identity and date first, so + the apply is reproducible (scripts/container-build.sh:34-44). + +Integrity controls: + +- `patches/SERIES.sha256` freezes every patch file's hash; + `scripts/build-audit.sh --freeze` regenerates it (scripts/build-audit.sh:11-23). +- The build stamps per-patch hashes into the shipped tree as + `ABLETON-WINE-PATCH-STACK.txt` and writes per-artifact hashes to + `BUILD-INFO` (scripts/container-build.sh:207-233). +- `scripts/build-audit.sh` diffs the shipped stamp against + `patches/SERIES.sha256` and checks per-patch binary fingerprints in the + built DLLs; it runs as the last build stage and again before installer + packaging (scripts/container-build.sh:267-268; scripts/make-installer.sh:26-27). + +Why it matters: any performance patch enters through this series, so its +audit trail is automatic. The `--3way` apply also means a base bump keeps +conflict resolution mechanical. + +## Compiler toolchain and flags + +The build uses a split toolchain, standard for Wine's WoW64 mode (running +64-bit and 32-bit Windows code without a 32-bit Linux userspace): + +| Component | Toolchain | Where defined | +| --- | --- | --- | +| Unix side (`.so` halves, wineserver) | gcc from `build-essential` | Containerfile:38-39; scripts/container-build.sh:49 | +| PE side (Windows-format DLLs) | clang/lld 21, exact package version pinned | Containerfile:13-17, 40-42 | +| Diagnostic PE probes | clang `-target x86_64-windows-gnu -fuse-ld=lld`, no CRT, `-O2` | tools/build_midihot.sh:14-23 | +| ableton-linkd | g++ `-std=c++17 -O2`, static libstdc++/libgcc | tools/build_ableton-linkd.sh:33-38 | +| PipeASIO objects | gcc `-O2 -fPIC -fvisibility=hidden -fno-strict-aliasing` | scripts/container-build.sh:150-158 | + +Key facts: + +- The LLVM pin is an exact apt package version + (`1:21.1.8~++20251221032842+...`, Containerfile:17). When apt.llvm.org ages + it out, the install fails loudly rather than drifting (Containerfile:14-16). +- ccache covers both gcc and clang through a shim directory; cache cap 5 GB + (Containerfile:71-92). CI reuses it across runs + (.github/workflows/ci-pr-build.yml:14-16). +- Wine itself is configured with + `CPPFLAGS="-I/opt/ntsync-uapi" ... --enable-archs=i386,x86_64 --disable-tests` + (scripts/container-build.sh:53-56). The repo passes no `CFLAGS` and no + optimization-level override for Wine; the build uses Wine's own defaults. +- Shipped binaries are stripped: `llvm-strip --strip-all` for PE files, + `strip --strip-unneeded` for Unix objects (scripts/container-build.sh:178-186). + +Why it matters: the pinned clang/lld pair decides PE codegen quality, and the +absence of project-set `CFLAGS` is the current ceiling on any +optimization-flag moonshot (see "Gaps and unknowns"). + +## Container build flow + +Builds run in Podman (`ENGINE` overridable, build.sh:12) inside an Ubuntu +22.04 image pinned by digest (Containerfile:10). All apt traffic after +bootstrap resolves against one Ubuntu snapshot, `20260718T000000Z` +(Containerfile:19, 31-34). + +`./build.sh` runs four stages (build.sh:24-47): + +1. Verify vendored inputs against pinned checksums (build.sh:25). +2. Build the container image (build.sh:28). +3. Run `scripts/container-build.sh` in the container with the repo mounted + read-only and `dist/` writable (build.sh:34-41). This script unpacks the + base, applies patches, configures and builds Wine (WoW64), builds PipeASIO + against the fresh Wine, strips and prunes, and packs a relocatable tarball + compressed with `zstd -19 --long=27` (scripts/container-build.sh:237). +4. Build `ableton-linkd` in the same image (build.sh:44; + scripts/build-ableton-linkd.sh:48-52). + +Two gates run before packaging finishes: + +- Relocation and registration gate: the packaged tree runs from a random path + and PipeASIO registers through Live's load path, with a stub PipeWire + library to satisfy the loader (scripts/container-build.sh:240-265). +- Build audit against the frozen series (scripts/container-build.sh:267-268). + +`scripts/make-installer.sh` then assembles the single-file `.run` installer: +a self-extracting header plus a tar payload carrying the runtime tarball, +scripts, desktop files, winetricks payloads, static cabextract, ableton-linkd, +and licence texts (scripts/make-installer.sh:1-4, 61-119). Releases never +rebuild Wine in CI; `scripts/release.sh` uploads the locally built artifacts +and a verify job re-downloads and checks them +(.github/workflows/release.yml:1-8). + +Build-time component gates that affect runtime capability: the build fails if +`winealsa.so` (ALSA MIDI), `winegstreamer.so` (mp3/mp4/wma import), the Push 2 +USB bridge, or ntsync support is missing (scripts/container-build.sh:63-123). +GStreamer codecs come from the host at runtime (Containerfile:59-62; +README.md:41). + +Why it matters: the flow is fully pinned and audited, so a moonshot change +gets a clean before/after artifact pair for free. The ccache mounts +(build.sh:31, 37) keep iteration fast. + +## Vendored inputs + +All external inputs are pinned in `vendor/` and checked by `make verify` +(Makefile:21-22) and build.sh:25. + +| Input | Version | Pin file | Used for | +| --- | --- | --- | --- | +| Wine base tarball | giang17 `5c23dd1c` (Wine 11.13 line) | vendor/wine-base.sha256 | the runtime | +| PipeASIO | 1.2.2 | vendor/pipeasio.sha256 | ASIO audio driver | +| PipeWire SDK (3 Ubuntu debs) | 1.6.2-1ubuntu1.1 | vendor/pipewire-sdk.sha256 | PipeASIO link-time headers/libs | +| ntsync UAPI header | kernel 6.14 API | vendor/ntsync-uapi.sha256 | kernel-sync build gate | +| Ableton Link SDK | 4.0 | vendor/link.sha256 | ableton-linkd | +| cabextract | 1.11 | vendor/cabextract.sha256 | static tool in installer | +| Bitstream Vera fonts | upstream 1.10, byte-identical | vendor/bitstream-vera.sha256 | Max for Live font fallback | +| winetricks | 20260125 | vendored script, vendor/winetricks:9 | prefix setup | + +The winetricks cache (`vendor/winetricks-cache/`) holds corefonts, vcrun2022, +and vcrun6 payloads for offline prefix setup (listing under +vendor/winetricks-cache/). Live 12 gets `corefonts vcrun2022 mfc42`; Live 11 +gets `corefonts vcrun2019 gdiplus` (scripts/setup-prefix.sh:279-285). + +## Audio stack: PipeASIO on PipeWire + +PipeWire is the Linux audio and video server. ASIO (Audio Stream Input/Output) +is Steinberg's low-latency Windows driver API; Live uses it for pro audio. +PipeASIO is an ASIO driver implemented as a Wine DLL that forwards to the +host's PipeWire. + +- Version 1.2.2, vendored (vendor/pipeasio-1.2.2.tar.gz). Two local patches: + keep the graph sample rate instead of failing with `ASE_NoClock`, and + report `timeGetTime` in ASIO systemTime (patches/SERIES.sha256:63-64). +- Built against the just-built Wine's headers (ABI-matched) and the vendored + PipeWire 1.6.2 SDK, with `winebuild`/`winegcc` producing the PE/Unix pair + (scripts/container-build.sh:131-176). +- The SDK is link-time only. The shipped `pipeasio64.dll.so` records + `DT_NEEDED libpipewire-0.3.so.0` and resolves against the user's PipeWire at + runtime; the enforced floor is 0.3.56, the first release with the + thread-utils API (Containerfile:100-105; scripts/container-build.sh:144-147, + 165-170). An rpath into the build container fails the build + (scripts/container-build.sh:167-170). +- Host requirement: PipeWire 0.3.56 or newer, 1.6+ recommended for audio + performance (README.md:40). +- Per-launch `PIPEASIO_*` overrides exist (BUILDING.md:77). +- ALSA MIDI rides Wine's `winealsa.drv`, which the build treats as mandatory + (Containerfile:53-58; scripts/container-build.sh:89-94). Patch 0028 + re-subscribes MIDI devices after reconnect (patches/BASE.txt:41-42). + +Why it matters: this is the latency-critical path. PipeASIO's data-loop +thread requests `SCHED_FIFO` priority 15 (see next section), and the +sample-rate patch removes a whole crash class on rate mismatch +(scripts/check-live-audio.sh:49-52). + +## Realtime scheduling and host tuning + +Realtime (RT) scheduling lets audio threads preempt normal processes. + +- Launcher: if `chrt -r 10 true` succeeds, Live starts as `chrt -r 10 wine` + (SCHED_RR priority 10) (scripts/ableton-live:780-782). `ABLETON_RT=off` + disables this for one launch (BUILDING.md:76). +- PipeASIO separately requests SCHED_FIFO 15 for its data-loop thread; + `ABLETON_RT=off` does not affect it + (notes/ABLETON-WINE-RT-SCHEDULING.md:4-8). The effect on low-core systems is + unmeasured (notes/ABLETON-WINE-RT-SCHEDULING.md:5-6). +- `scripts/setup-realtime.sh` installs the host profile: PAM limits rtprio 95, + memlock unlimited, nice -19 for the RT group (scripts/setup-realtime.sh:72-77); + `vm.swappiness = 10` (scripts/setup-realtime.sh:80-83); a systemd unit + forcing the `performance` CPU governor (scripts/setup-realtime.sh:106-122). +- The script advises, but never applies, the `threadirqs` kernel parameter and + a lowlatency/PREEMPT_RT kernel (scripts/setup-realtime.sh:124-151). A + wineserver `chrt -f 95` boost is deliberately left out and kept as a manual + A/B experiment (scripts/setup-realtime.sh:22-25). + +Why it matters: scheduling is the cheapest latency lever, and the repo +already has the probe-and-grant structure a moonshot can extend. + +## ntsync + +ntsync is a Linux kernel driver (merged in kernel 6.14) that implements +Windows NT synchronization primitives in the kernel. Without it, every NT +sync wait becomes a round trip to wineserver, Wine's userspace coordinator +process. + +- The build needs `linux/ntsync.h`, which Ubuntu 22.04's 5.15 headers lack, so + the header is vendored and sha256-pinned (Containerfile:94-98). +- The build hard-fails if `HAVE_LINUX_NTSYNC_H` is unset or if either + wineserver or `ntdll.so` lacks ntsync references; the comment records the + cost of shipping without it as ~1.3 cores of wineserver traffic with Live + running (scripts/container-build.sh:104-123). Two 2026-07 builds shipped + this regression unnoticed (scripts/container-build.sh:104-107; + notes/ABLETON-WINE-NTSYNC-REGRESSION.md). +- Runtime verification: `scripts/check-ntsync.sh` runs `ntsyncprobe.exe` + against a scratch prefix, checks sync semantics, and confirms wineserver + holds an open `/dev/ntsync` fd when the device exists + (scripts/check-ntsync.sh:1-6, 52-76). + +Why it matters: ntsync is the single biggest sync-overhead reducer in the +stack, and the repo has both the gates and the probe to A/B it. + +## Ableton Link + +Ableton Link is a LAN protocol that synchronizes tempo and beat position +between music applications. Discovery rides multicast group 224.76.78.75, UDP +port 20808 (tools/ableton-linkd.cpp:3; scripts/setup-link.sh:26-28). + +- `ableton-linkd` is a small native C++17 daemon built from the vendored + Ableton Link 4.0 SDK (GPLv2+, corresponding source ships in the installer; + scripts/make-installer.sh:93-102; https://github.com/Ableton/link). It is + the longest-lived session peer: it holds tempo and timeline across Live + restarts and relays Start Stop Sync without owning a transport + (tools/ableton-linkd.cpp:1-16). It is strictly passive after construction + (tools/ableton-linkd.cpp:13-15). +- Modes: foreground anchor, `--daemon`, and `--probe` for a scriptable + peers/tempo verdict (tools/ableton-linkd.cpp:21-28). +- It runs as a systemd user unit with restart-on-failure + (scripts/ableton-linkd.service), or the launcher starts it when systemd is + unavailable (scripts/setup-link.sh:95-96). +- Setup opens UDP 20808 in ufw or firewalld when one is active; no multicast + route is needed because the Link SDK binds discovery sockets with + `IP_MULTICAST_IF` (scripts/setup-link.sh:6-12, 25-44). +- `tools/jacklinkd.c` exists as a JACK-transport-related utility; the daemon + header notes native apps join the session directly and upstream jack_link + remains an option (tools/ableton-linkd.cpp:17-19). + +Why it matters: the anchor removes session-reestablishment stalls when Live +restarts, and the native peer sidesteps running the Link stack under Wine. + +## Desktop and OS integration + +XDG portals are D-Bus services that let sandboxed or foreign toolkits use +native host dialogs. Desktop files and MIME entries register apps and file +types with the Linux desktop. + +- File dialogs: patch 0031 adds an XDG file-dialog portal backend to comdlg32 + (patches/BASE.txt:48-51). `bin/set-file-portal-policy` sets the + `FileDialogPortal` registry policy (bin/set-file-portal-policy:20-23). + `bin/ableton-live-portal` and `bin/ableton-wine-portal` launch a separate + `-portal` runtime variant with `WINE_FORCE_PORTAL=1` as an option + (bin/ableton-live-portal:4, 29-31). +- File-manager integration: patches 0043, 0063, and 0064 route Live's "Show in + Explorer" requests through `org.freedesktop.FileManager1` and the OpenURI + portal (patches/BASE.txt:91-94, 229-240). +- Desktop entries and MIME types for Live sets/clips/packs, the `ableton://` + URL scheme, and `.auz` authorization files are defined under `desktop/` + (desktop/ableton-live.desktop.in:9-10; desktop/x-wine-extension-auz.xml:3-6). + The launcher repairs handler entries a foreign prefix hijacked + (scripts/ableton-live:52-73). +- Push 2 display: patch 0032 adds a host libusb-1.0 bridge exporting the + 16-function Win64 ABI `Push2DisplayProcess.exe` needs; the build verifies + every export ordinal (patches/BASE.txt:52-55; + scripts/container-build.sh:63-87). +- WebView2 (Chromium embed for Live's Learn View): the launcher forces the + SwiftShader software-rendering path and `--no-sandbox` + (scripts/ableton-live:39-42). Software rendering here is a correctness fix, + not a performance choice. +- Display scale: `scripts/detect-scale.sh` probes GNOME, KDE, sway, Hyprland, + COSMIC, and Xft.dpi, and the launcher recalibrates prefix DPI each launch + (scripts/detect-scale.sh:1-5; scripts/ableton-live:199-204). +- Theme: `scripts/detect-theme.sh` reads the XDG settings portal and GNOME + gsettings; a watcher thread re-syncs Win32 colors live + (scripts/detect-theme.sh:1-6; scripts/ableton-live:437-441). +- Fonts: vendored Bitstream Vera terminates Max for Live's font fallback + chain; without it an M4L device can hang Live (scripts/make-installer.sh:78-89; + scripts/check-m4l-fonts.sh:1-13). + +Why it matters: portal dialogs and native file-manager calls remove slow or +fragile Wine fallbacks; the USB bridge and DPI calibration are stability +work, and the launcher knob set below is performance work. + +## Launcher runtime knobs + +`scripts/ableton-live` exports a fixed environment per launch. The +performance-relevant subset: + +| Variable | Default | Effect | Where | +| --- | --- | --- | --- | +| `WINEDEBUG` | `-all` | Disables Wine debug logging; fixme spam stalls Live's UI thread | scripts/ableton-live:16-17 | +| `WINE_D3D_CONFIG` | `csmt=0x1` | Enables wined3d's command-stream thread | scripts/ableton-live:18 | +| `WINED3D_DCOMP_FORCE_FULL_REDRAW` | `1` | Full-redraw mode for the DComp stack | scripts/ableton-live:19 | +| `WINE_X11_FORCE_OFFSCREEN_CLASS` | `Ableton Live Window Class` | Keeps Live on winex11's offscreen path (M4L flicker fix) | scripts/ableton-live:20-23 | +| `WINE_DISABLE_UNIX_MOUNT_REPARSE` | `1` | Mount points appear as plain dirs | scripts/ableton-live:24-26 | +| `WINE_CPU_TOPOLOGY` | capped at 8 | Groundwork only: no consumer in this runtime yet | scripts/ableton-live:75-108 | +| `WINEDLLOVERRIDES` | `mscoree,mshtml=` | Keeps Mono/.NET and HTML-help hooks out of Live | scripts/ableton-live:29-32 | + +Patch 0055 adds `WINE_DISABLE_GL_PRESENT=1` as an escape hatch back to the +GDI present path (patches/BASE.txt:176-177). + +## Benchmarking and diagnostics + +An xrun is an audio buffer under- or overrun, heard as a click or dropout. +DSP load is Live's own audio-engine utilization meter. + +- `scripts/bench-run.sh` appends one CSV row per measurement under fixed + reference conditions (committed reference set, 48 kHz / 256 frames, fixed + window geometry). Two metrics are automated: `wined3d_cs` thread %CPU from + 60 s of `top` samples, and the wineserver context-switch delta over 60 s. + Two are operator-entered: xruns per 5 minutes from `pw-top`'s ERR delta, + and Live's DSP load reading. The unit of evidence is a before/after pair + committed with the change (scripts/bench-run.sh:1-17, 52-103). No + `bench/` results directory exists in the repo. +- Checks: `scripts/check-ntsync.sh` (sync semantics and ntsync activity), + `scripts/check-live-audio.sh` (Live opens PipeASIO without a FatalError; + scripts/check-live-audio.sh:1-3), `scripts/check-m4l-fonts.sh` (font + fallback regression tests; scripts/check-m4l-fonts.sh:1-13), + `scripts/build-audit.sh` (patch-stack provenance). +- Beta tester kit: `beta/tester-kit/run-session` collects a redacted system + report, installs the build, and runs checksum-verified probes: shared-session + allocator stress, menu/resize convergence, OpenGL child/sRGB, portal dialog, + MIDI replug, DPI metrics, and optional live-Live window probes + (beta/tester-kit/README.md:14-32, 59-81). Probe sources and PE binaries live + under `beta/tester-kit/probes/`, rebuilt by maintainers against a Wine build + tree with clang and LLD (beta/tester-kit/README.md:101-115). +- Cross-platform profilers for issue reports: Linux, macOS, and Windows + scripts in `beta/scripts/` (beta/scripts/ listing; + beta/scripts/ableton-linux-profiler.sh:1-3). +- `tools/` holds about 40 diagnostic utilities: PE probes built like Wine's + own PE modules (swamprobe, liveinject, midihot, linkprobe, mousespy, + setsyscolors, learnheal, webviewclose, metricprobe2 — the nine with + `build_*.sh` scripts), plus X11-side helpers (xmon, xdrag, xclose, ukey, + uidrag) and one-off probes without build scripts. `tools/m4l-hang-capture.sh` + and `tools/m4l-font-audit.py` target Max for Live stalls. + +Why it matters: the bench harness defines the project's evidence standard — +every moonshot claim needs a committed before/after pair — but the automated +metrics cover only wined3d_cs CPU and wineserver context switches. + +## Gaps and unknowns + +- The exact diff between giang17's `d2d1-dcomp-11.13` branch and WineHQ's + 11.13 release is not determinable from this repo; only the vendored binary + tarball is pinned, not a verifiable git reference. +- The repo sets no `CFLAGS` or optimization level for the Wine build itself + (only `CPPFLAGS`, scripts/container-build.sh:53-56). What `-O` level and + target flags Wine's configure picks for this tree is unverified here. +- No compiler-based hardening or LTO settings are visible anywhere in the + build; whether clang LTO is feasible for the PE side is unexplored in the + repo. +- `WINE_CPU_TOPOLOGY` is exported by the launcher but is inert: the patched + ntdll/wineserver consumer has not landed (scripts/ableton-live:78-79). Its + intended design is not documented in the repo. +- Whether this Wine tree supports esync/fsync is not stated anywhere; the + wrappers only unset `WINEESYNC`/`WINEFSYNC` defensively + (bin/ableton-wine-portal:15). ntsync is the documented sync path. +- The `-portal` runtime variant (`wine-d2d1-nspa-11.13-portal`, + bin/ableton-live-portal:4) has no build recipe in this repo; how it differs + from the main build beyond patch 0031 is undetermined. +- No committed benchmark data exists (`bench/results.csv` is created on first + use; scripts/bench-run.sh:97-101). There is no performance baseline history + in the repo, only per-release notes. +- The automated bench metrics omit PipeASIO-level latency (round-trip latency, + callback period jitter). pw-top ERR deltas are operator-entered + (scripts/bench-run.sh:34, 48-50). +- The RT effect on low-core-count systems is unmeasured + (notes/ABLETON-WINE-RT-SCHEDULING.md:5-6), and the wineserver priority boost + is an untested manual experiment by policy (scripts/setup-realtime.sh:22-25). +- Most `tools/*.c` utilities have no build script in the repo (only nine + `build_*.sh` exist); their build commands are unrecorded. +- Which glibc floor the shipped binaries actually require is asserted as + Ubuntu 22.04 / glibc 2.35 (scripts/container-build.sh:230; README.md:39) + but not mechanically gated beyond the container base. diff --git a/notes/performance-moonshot/docs/PROTON-GRAPHICS-AND-RUNTIME.md b/notes/performance-moonshot/docs/PROTON-GRAPHICS-AND-RUNTIME.md new file mode 100644 index 00000000..65877021 --- /dev/null +++ b/notes/performance-moonshot/docs/PROTON-GRAPHICS-AND-RUNTIME.md @@ -0,0 +1,339 @@ +# Proton's graphics and runtime work, judged for a DAW under Wine + +This document helps the reader decide which of Valve Proton's performance +techniques are worth adopting for ableton-wine, and which are game-only and +can be ignored. It covers DXVK, vkd3d-proton, DXVK-NVAPI, Fossilize shader +pre-caching, Proton's compiler flags, allocator and large-page work, FAudio +versus winepulse, Proton-GE additions, the pressure-vessel container, and +frame-latency work, then maps each onto what Ableton Live and its plugin +editors actually do in this stack. + +Terms used below: a *translation layer* reimplements one graphics API on top +of another (Direct3D on Vulkan, or Direct3D on OpenGL). A *present* is the +step where a finished frame is handed to the display; a *swapchain* is the +queue of frames behind a present. A *shader* is a small GPU program an app +compiles at runtime; *shader pre-caching* compiles them before launch. + +## What Proton is, in 2026 + +Proton is Valve's Wine fork for games, shipped through Steam with a graphics +stack (DXVK, vkd3d-proton, DXVK-NVAPI), a container runtime +(pressure-vessel), and per-game workarounds. Proton 11 shipped around +2026-04 with ARM64 support via the FEX emulator +([igor'sLAB](https://www.igorslab.de/en/the-new-proton-update-now-includes-support-for-arm64-devices-as-well-as-other-new-features/)). +Its community fork GE-Proton is at GE-Proton11-1 (2026-06-24, per +[shattered.io](https://shattered.io/heroic-vs-lutris/); see the +[releases page](https://github.com/GloriousEggroll/proton-ge-custom/releases)). + +Proton optimizes one workload: fullscreen 3D games that render thousands of +GPU-bound frames per second through D3D9-12. A DAW is the opposite +workload: a 2D UI that must not stall, an audio engine that must never +miss a deadline, and plugin windows that mix OpenGL, D3D, and embedded +Chromium. That difference decides every verdict below. + +## The graphics stack this project already runs + +Facts about the current stack, from this repository: + +- Live's own UI uses its Direct2D/Direct3D 11 renderer on Wine's built-in + wined3d, which translates D3D11 to OpenGL + (`notes/ABLETON-WINE-GPU-RENDERER.md:8-12`). +- Presents of Live's main window take a direct GL path: patch 0055 marks + top-level swapchains `WINED3D_SWAPCHAIN_PREFER_GL_PRESENT` and Wine shows + frames with `glXSwapBuffers`, skipping a 14 MB-per-frame GPU-to-CPU copy + that cost about 650 MB/s of display traffic + (`notes/ABLETON-WINE-GPU-RENDERER.md:64-86`). Patches 0058 and 0059 keep + that path correct under fractional scaling + (`notes/ABLETON-WINE-GPU-RENDERER.md:99-171`). +- WebView2 panes (Learn View, Splice) are software-rendered: Live hardcodes + `--disable-gpu --disable-gpu-compositing --disable-direct-composition` + into its browser processes + (`notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md:14-16`). No GPU + translation layer can accelerate them. +- OpenGL plugin editors (JUCE/OpenGL, e.g. CHOW Tape Model) render through + GLX directly and are composited by winex11; the crash fixed by patch 0026 + was an X11 depth mismatch, not a translation-layer problem + (`notes/ABLETON-WINE-GL-PLUGIN-EDITOR-CRASH-BUG.md:21-36`). +- WebView2 plugin editors (Splice INSTRUMENT) crash on an OLE drag-drop + pointer, fixed in ole32 by patch 0045 — again not a graphics problem + (`notes/ABLETON-WINE-WEBVIEW2-PLUGIN-CLOSE-CRASH.md:10-24`). +- The audio engine does not touch Wine's audio drivers: it runs ASIO through + PipeASIO into PipeWire (`scripts/ableton-live:760-762`, + `scripts/container-build.sh:131-176`). +- Synchronization already runs on ntsync, the kernel driver that grew out of + Proton's esync/fsync work (`notes/ABLETON-WINE-NTSYNC-REGRESSION.md:10-14`, + `scripts/container-build.sh:104-123`). + +## DXVK + +DXVK reimplements D3D9/10/11 on Vulkan. It is the single biggest +performance lever Proton has for games. Current release is 2.7.1 +(2025-08-30); the next release was already overdue in 2026-01 +([DXVK issue 5433](https://github.com/doitsujin/dxvk/issues/5433), +[Debian package tracker](https://tracker.debian.org/dxvk)). DXVK 2.7 +deprecated its descriptor-buffer path and requires current drivers +([DXVK releases](https://github.com/doitsujin/dxvk/releases)). + +Relevance here: partial, and unproven. Live's UI is D3D11, so DXVK could in +principle replace the wined3d-GL translation. wined3d serializes rendering +on one command-stream thread (the "CS thread" of +`notes/ABLETON-WINE-GPU-RENDERER.md:108-116`); DXVK's submission model is +more parallel, and its per-frame CPU cost is lower for heavy D3D11 +workloads. Live's UI is not a heavy D3D11 workload — it is a 2D desktop UI +— so the expected gain is smaller than in games and may be near zero. + +Risks are concrete, not theoretical: + +- Patch 0055's direct GL present, and gates 0058/0059, live in wined3d's GL + swapchain path. Under DXVK, d3d11 presents go through Vulkan swapchains + instead; all three patches become dead code, and the 650 MB/s copy + problem they solved (issue 91) needs a new answer. +- Live gates its GPU renderer on the device name wined3d reports, and this + project ships patches 0057/0061 to pass that check + (`notes/ABLETON-WINE-GPU-RENDERER.md:173-202`). DXVK reports adapter + names from Vulkan instead. Unverified: whether Live's check passes under + DXVK, on every GPU family this project supports. +- DXVK requires a working Vulkan driver; the GDI fallback and the + wined3d-GL path work everywhere. A Vulkan failure would grey out or break + the UI on machines that work today. + +Cheaper experiment first: wined3d has its own Vulkan backend, selectable +without swapping DLLs. Unverified: whether that backend works with Live's +D2D usage and with the dcomp patches (0041). Either way, the test is a +prefix-level A/B — the vendored winetricks already carries dxvk verbs +(`vendor/winetricks:6887-6900`) — and does not require a rebuild. + +## vkd3d-proton + +vkd3d-proton reimplements D3D12 on Vulkan. Current release is 3.0.1 +(2026-05), which added experimental view instancing and Vulkan present +timing ([Phoronix](https://www.phoronix.com/news/VKD3D-Proton-3.0.1)); +3.0 (2025-11) brought FSR4 support and a DXBC shader-backend rewrite +([9to5Linux](https://9to5linux.com/vkd3d-proton-3-0-released-with-fsr4-support-dxbc-shader-backend-rewrite)). + +This does not apply. Live's renderer is D3D11, not D3D12 +(`notes/ABLETON-WINE-GPU-RENDERER.md:11-12`). No mainstream VST plugin +editor uses D3D12. Unverified: whether any future plugin editor (game-engine +-based UIs) will need D3D12; worth a watch item, not work now. + +## DXVK-NVAPI + +DXVK-NVAPI implements NVIDIA's NVAPI (driver feature library: GPU queries, +Reflex latency reduction, DLSS hooks) on top of DXVK/Vulkan. It is actively +maintained — 0.9.1 shipped 2026-01 and 0.9.2 followed in 2026-05 with +experimental D3D12 shader extensions +([Phoronix Linux gaming archive](https://www.phoronix.com/linux/Linux+Gaming), +[dxvk-nvapi releases](https://github.com/jp7677/dxvk-nvapi/releases)). + +This does not apply. NVAPI's features target games (latency reduction for +competitive play, upscaling). A DAW UI has no use for them, and the project +already solves its NVIDIA-specific problems in Wine itself. + +## Shader pre-caching (Fossilize) + +Fossilize is Valve's Vulkan pipeline recorder/replayer. Steam records the +Vulkan pipelines a game creates, ships them to other machines, and compiles +them before the game starts, removing first-run shader-compile stutter +([Fossilize repository](https://github.com/ValveSoftware/Fossilize)). It is +still Steam's mechanism in 2026, with known rough edges on NVIDIA +([NVIDIA developer forum, 2026-02](https://forums.developer.nvidia.com/t/steam-fossilize-doesnt-seem-to-work-on-nvidia-cards/359282)). + +This does not apply. Fossilize only records Vulkan pipelines. This stack's +GPU work goes through OpenGL (wined3d-GL, GLX plugin editors), and the +WebView2 panes are software-rendered +(`notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md:14-16`). wined3d +already keeps its own on-disk GLSL shader cache. Live's UI uses a small, +fixed set of shaders; game-scale shader stutter does not exist here. If +DXVK is ever adopted (see DXVK section), DXVK's own state cache plus +Mesa's driver cache cover the same ground without Fossilize. + +## Proton's compiler flags and build optimizations + +Verified from the Proton source tree (branch `proton_10.0`, commit +`e91ca2be`, 2026-07-27; [Makefile.in](https://github.com/ValveSoftware/Proton/blob/proton_10.0/Makefile.in)): + +| Setting | Proton 10 value | Evidence | +|---|---|---| +| Optimization | `-O2 -fwrapv -fno-strict-aliasing` | Makefile.in:59 | +| Arch tuning | `-march=nocona -mtune=core-avx2 -mfpmath=sse` (plus `-mstackrealign` i386, `-mcmodel=small` x86_64) | Makefile.in:56-57 | +| Debug info | `-ggdb -ffunction-sections -fdata-sections -fno-omit-frame-pointer`; stripped at install | Makefile.in:60, 44-51 | +| LTO | Not enabled for Wine; explicitly disabled for vkd3d ("causes the build to fail") | Makefile.in:669-671 | +| PGO | None found | whole-tree grep, unverified beyond `proton_10.0` | + +Valve builds for the oldest x86-64 (nocona is the 2004 baseline) with a +modern tuning target, at `-O2`, without LTO or PGO. Downstream forks go +further: proton-cachyos ships `-O2 -march=x86-64-v3` and `-O3` LLVM +variants ([proton-cachyos CI, 2026-02](https://github.com/CachyOS/proton-cachyos/actions/runs/21804618854)). + +This project currently passes no CFLAGS at all: `scripts/container-build.sh:53-56` +runs configure with only the ntsync `CPPFLAGS`, so Wine's own defaults +apply; PipeASIO is built `-O2 -DNDEBUG` +(`scripts/container-build.sh:151-158`). The toolchain is gcc for the Unix +side, pinned clang 21/lld for the PE side (`Containerfile:37-42`), and the +output is stripped (`scripts/container-build.sh:183-186`). + +Judgment: Proton's flags are a conservative, proven baseline this project +can match for free, and the arch tuning is the one real delta. Caveat: most +CPU in a Live session burns inside `Live.exe` and plugin code, not inside +Wine's DLLs. Flags only speed up Wine's own code paths (wined3d translation, +dcomp blits, wineserver, heap). Expected gain is small and must be measured +with the project's existing probes, not assumed. + +## Memory allocator and large pages + +Proton does not replace Wine's allocator. Its allocator-adjacent options +are game workarounds, not optimizations: `PROTON_HEAP_DELAY_FREE` delays +frees to mask use-after-free bugs, and `PROTON_FORCE_LARGE_ADDRESS_AWARE` +sets a PE header flag ([GE-Proton README](https://github.com/GloriousEggroll/proton-ge-custom/blob/master/README.md)). +Neither speeds anything up. + +On large pages: a request for transparent huge page (THP) support in Proton +has existed since 2022 ([Proton issue 5816](https://github.com/ValveSoftware/Proton/issues/5816)). +Unverified: its current state as of 2026-08 (GitHub API was rate-limited at +research time); no shipped Proton or Wine release implements large-page PE +mappings. There is nothing finished to adopt. A THP experiment is still +possible at the OS level (see Key opportunities): Live plus its plugins +hold multi-GB sample and DSP buffers in ordinary malloc memory, where 2 MB +pages cut TLB misses. Whether that matters for Live's worst-case audio +deadline is unknown until measured. + +## FAudio versus winepulse + +These are different layers that Proton comparisons often conflate: + +- FAudio reimplements Microsoft's XAudio2 API. Wine has carried it in-tree + since Wine 4.3 (2019) + ([GamingOnLinux](https://www.gamingonlinux.com/2019/03/wine-43-is-out-with-the-xaudio2-reimplementation-faudio-included/)), + and standalone FAudio releases continued at least through 25.09 + ([AUR faudio package](https://aur.archlinux.org/packages/faudio)). + Games use XAudio2; Live does not. +- winepulse is Wine's mmdevapi (WASAPI) backend that talks to + PulseAudio/PipeWire. This build includes it for Wine's own audio + (`Containerfile:55-58`). + +Neither is on the DAW's performance path. Live's engine opens the ASIO +device from PipeASIO, a native PipeWire client with no JACK layer +(`scripts/ableton-live:760-762`). winepulse only carries incidental sound: +WebView2 pane audio, plugin UIs that play preview audio through WASAPI. +There is nothing to optimize here; the Proton FAudio question is a +non-issue for this project. + +## Proton-GE additions + +GE-Proton adds, on top of Valve's Proton +([GE-Proton README](https://github.com/GloriousEggroll/proton-ge-custom/blob/master/README.md)): + +| Addition | Purpose | Applies to a DAW? | +|---|---|---| +| Media Foundation patches | game video cutscenes | Only marginally — Live's media import already works through winegstreamer (`scripts/container-build.sh:96-102`) | +| `WINE_FULLSCREEN_FSR` upscaling | game rendering | No | +| NVIDIA CUDA / PhysX / NVAPI | game physics, DLSS | No | +| Raw input patches | game mouse input | No — MIDI and mouse already work; see `notes/ABLETON-WINE-INPUT-BUG.md` for the actual input work | +| protonfixes per-game fixes | game-specific hacks | The pattern applies (this repo's patch series is the same idea), the content does not | +| wine-staging backports | assorted | Case by case; this repo already curates its own series | +| NTSync enablement | synchronization | Already done here (`notes/ABLETON-WINE-NTSYNC-REGRESSION.md:50-56`) | + +GE-Proton itself is not a candidate runtime: it targets games inside +Steam's container, and running it outside Steam is only supported through +umu with the full container environment (same README). + +## The pressure-vessel container runtime + +pressure-vessel is the container launcher that runs Proton against Steam +Linux Runtime, a fixed library set, so games see identical libraries on +every distribution ([Valve's steam-runtime known-issues doc](https://github.com/ValveSoftware/steam-runtime/blob/master/doc/steamlinuxruntime-known-issues.md)). +It is actively developed — Steam Linux Runtime 3.0 (sniper) was updated in +2026-06 including arm64 pressure-vessel builds +([SteamDB patch notes, 2026-06-03](https://steamdb.info/patchnotes/23181402/)). + +This solves a problem the project already solved differently. The build is +fully pinned (base image digest, Ubuntu snapshot, exact LLVM package, +sha256-checked vendored inputs; `Containerfile:1-19`) and ships a +relocatable tarball proven by a relocation gate +(`build.sh:15-17`, `scripts/container-build.sh:240-265`). A DAW makes the +container trade worse than a game does: the runtime must reach the host's +PipeWire sockets, ALSA MIDI devices, USB (Push 2), real-time scheduling, +and the XDG file portal. Every one of those is a hole punched through the +container or a failure mode. Containerizing would add namespace setup to +startup and a new class of device bugs, in exchange for library isolation +the pinned build already provides. Do not adopt. + +## Frame-latency work + +Proton's frame-latency work targets games: DXVK's `maxFrameLatency` and +frame-rate limiter, vkd3d-proton 3.0.1's Vulkan present timing +([Phoronix](https://www.phoronix.com/news/VKD3D-Proton-3.0.1)), and +gamescope, Valve's nested compositor for fullscreen presentation. Present +timing lets an app schedule exactly when a frame lands. + +For a DAW, the latency that matters is the audio callback deadline, which +none of this touches. UI frame latency affects feel, not correctness. This +project's own present-path work — the direct GL present of patch 0055 with +the 0058/0059 gates — is the equivalent optimization, already done and +measured (`notes/ABLETON-WINE-GPU-RENDERER.md:158-171`). Present timing +only exists for Vulkan, so it becomes relevant only if the DXVK experiment +lands. Gamescope does not apply: Live is a multi-window desktop app with +plugin-editor windows, portals, and WebView2 children, not a single +fullscreen surface. + +## What applies and what does not + +| Proton technique | Verdict for ableton-wine | One-line reason | +|---|---|---| +| DXVK (D3D11→Vulkan) | Worth one measured A/B, unproven | Live's UI is D3D11, but patches 0055/0058/0059 and the device check assume wined3d | +| wined3d Vulkan backend | Cheaper A/B than DXVK | Same translation goal, no DLL swap; compatibility unverified | +| vkd3d-proton | Does not apply | No D3D12 anywhere in this workload | +| DXVK-NVAPI | Does not apply | Game latency/upscaling features | +| Fossilize pre-caching | Does not apply | Vulkan-only; no shader-stutter problem exists here | +| Proton compiler flags | Adopt the baseline, test arch tuning | `-O2`/nocona/core-avx2 is proven; this build passes no CFLAGS at all | +| LTO / PGO | Not a proven lever | Valve avoids LTO; no PGO exists upstream | +| Allocator swap | Nothing to adopt | Proton's heap options are game bug workarounds | +| Large pages (THP) | OS-level experiment only | No upstream implementation exists; benefit unverified | +| FAudio / winepulse | Does not apply | Audio runs ASIO→PipeASIO→PipeWire, outside both | +| GE-Proton additions | Reject as a runtime | Game-targeted; Steam-container oriented | +| pressure-vessel | Do not adopt | Pinned relocatable tarball already solves isolation; DAW needs host devices | +| Frame-latency / gamescope | Does not apply (except as DXVK follow-up) | Equivalent present-path work already shipped in 0055/0058/0059 | +| ntsync lineage | Already shipped | `notes/ABLETON-WINE-NTSYNC-REGRESSION.md` | + +## Key opportunities + +1. **Match and extend Proton's compiler flags in the container build.** + Add Proton's baseline (`-O2 -fwrapv -fno-strict-aliasing + -march=nocona -mtune=core-avx2 -mfpmath=sse`) to the Wine configure in + `scripts/container-build.sh:53-56`, then benchmark an + `-march=x86-64-v2` or v3 variant against it using the existing probes + (`beta/tester-kit/probes/src/ntsyncprobe.c`, the present-bandwidth + measurement from issue 91). Impact: low to medium (Wine-internal code + only). Effort: low. Evidence: Proton's verified flags at + [Makefile.in:56-60](https://github.com/ValveSoftware/Proton/blob/proton_10.0/Makefile.in) + versus no CFLAGS in `scripts/container-build.sh:53-56`. +2. **A/B test DXVK for Live's D3D11 UI on a prefix copy, with the + wined3d Vulkan backend as the cheaper first step.** Measure Live CPU + during continuous UI activity against the numbers in + `notes/ABLETON-WINE-GPU-RENDERER.md:158-171`, check the device-name gate + (`notes/ABLETON-WINE-GPU-RENDERER.md:173-202`), and verify the WebView2 + panes and GL plugin editors. Abandon if the check greys out or the + panes regress. Impact: medium if it works, likely low. Effort: medium + (prefix-level, no rebuild; `vendor/winetricks:6887-6900` has dxvk + verbs). Evidence: DXVK's parallel submission versus wined3d's single CS + thread (`notes/ABLETON-WINE-GPU-RENDERER.md:108-116`). +3. **Run a transparent-huge-pages experiment on the audio workload.** + Compare Live's audio-thread xrun count and `perf` TLB-miss rates with + THP `madvise` mode versus `never`, on a large session. If it helps, + ship a launcher-side prctl/madvise wrapper rather than a system-wide + change. Impact: low to medium, unverified. Effort: low. Evidence: no + upstream implementation exists to copy + ([Proton issue 5816](https://github.com/ValveSoftware/Proton/issues/5816)); + Live holds multi-GB sample buffers in normal pages. +4. **Skip, on the record: vkd3d-proton, DXVK-NVAPI, Fossilize, FAudio + work, GE-Proton as a runtime, pressure-vessel, and gamescope.** Spending + no effort here is itself a decision this document supports; each is + game-only or already solved by the pinned tarball and PipeASIO + (`scripts/container-build.sh:240-265`, `scripts/ableton-live:760-762`). + Impact: high (saved effort). Effort: none. Evidence: the verdict table + above. +5. **Track vkd3d-proton's present timing as a watch item, not work.** It + matters only if the DXVK experiment (item 2) lands and UI frame pacing + ever measures as a problem. Impact: low. Effort: none beyond reading + release notes. Evidence: vkd3d-proton 3.0.1 added Vulkan present timing + ([Phoronix](https://www.phoronix.com/news/VKD3D-Proton-3.0.1)). diff --git a/notes/performance-moonshot/docs/PROTON-SYNC-AND-CPU.md b/notes/performance-moonshot/docs/PROTON-SYNC-AND-CPU.md new file mode 100644 index 00000000..b7f7f7be --- /dev/null +++ b/notes/performance-moonshot/docs/PROTON-SYNC-AND-CPU.md @@ -0,0 +1,289 @@ +# Proton synchronization and CPU techniques for the performance moonshot + +This document helps the reader decide which of Valve Proton's synchronization +and CPU-side techniques are worth porting into ableton-wine, and which the +project already ships. Each technique gets: what it does, upstream status as +of August 2026, whether this repo has it, and how it applies to Ableton Live. + +Terms used below: *wineserver* is Wine's single-threaded user-space process +that emulates the Windows kernel's object store; every classic NT +synchronization wait (mutex, semaphore, event) used to be a remote procedure +call (RPC) to it over a Unix socket. A *futex* is Linux's fast user-space +locking primitive; *eventfd* is a Linux kernel notification object exposed as +a file descriptor. *SCHED_FIFO* and *SCHED_RR* are Linux realtime scheduling +policies; *rtkit* (RealtimeKit) is a D-Bus service that grants realtime +priority to unprivileged processes. + +## Wineserver architecture and its cost + +Wine emulates Windows kernel objects in a separate process, `wineserver`. +It is single-threaded and event-driven. Every `WaitForSingleObject`, +`ReleaseMutex`, or `SetEvent` on a non-cached object crosses a socket +boundary twice (request and reply), serializing all clients through one +thread. For a game making thousands of synchronization calls per second this +was a long-standing CPU bottleneck +(https://www.xda-developers.com/wine-11-rewrites-linux-runs-windows-games-speed-gains/). + +This repo measured the cost directly. With ntsync missing and Live idle at a +256-frame ASIO buffer, wineserver used about 45% of one core and handled +about 9,000 context switches per second; restoring ntsync raised +synchronization throughput 4 to 50 times +(notes/ABLETON-WINE-NTSYNC-REGRESSION.md:10-14). + +ntsync removes *handle waits* from wineserver, but not everything: +alertable sleeps and APC (asynchronous procedure call — a callback Windows +queues onto a specific thread) delivery still go through the server +(notes/ABLETON-WINE-APC-COALESCING.md:14-18). Live's APC coalescing thread +idles at 30 to 40% of one core, and the single-threaded wineserver +serializing per-APC wakeups is the current hypothesis for a playback fault +(notes/ABLETON-WINE-APC-COALESCING.md:24-28). The registry, window +management, and cross-process handle bookkeeping also stay in wineserver. + +## esync + +*What it does:* a Wine patch set by Elizabeth Figura (CodeWeavers, 2018) +that replaces wineserver round trips for events/semaphores/mutexes with +per-object `eventfd` descriptors handled in-process +(https://www.xda-developers.com/wine-translating-windows-games-linux-proton-effortless/). + +*Upstream status:* never merged into upstream Wine; shipped in +wine-staging through version 10.15 (not enabled by default) and in Proton, +where it is the fallback when fsync is unavailable +(https://wiki.archlinux.org/title/Wine). Known downside: one file descriptor +per synchronization object, so descriptor-hungry apps hit `ulimit -n`. + +*In this repo:* no. `scripts/setup-prefix.sh:56` explicitly unsets +`WINEESYNC`/`WINEFSYNC`, and the base is plain upstream Wine +(patches/BASE.txt:1-5). Superseded by ntsync; no reason to add it. + +*Ableton applicability:* none beyond historical. ntsync is strictly better: +no fd pressure, correct "wait-all" semantics, already shipped here. + +## fsync and futex2 / futex_waitv + +*What it does:* fsync is Figura's follow-up patch set that emulates NT waits +with futexes in shared memory, faster than esync and without the fd cost. It +originally needed an out-of-tree kernel op (`FUTEX_WAIT_MULTIPLE`). The +upstream substitute, `futex_waitv()` from the "futex2" series by André +Almeida (Collabora), landed in Linux 5.16 in 2022 +(https://lwn.net/Articles/1056417/, +https://docs.kernel.org/userspace-api/futex2.html). Proton's fsync uses +`futex_waitv` on kernels 5.16+; fsync remains Proton-only, never merged into +upstream Wine (https://wiki.archlinux.org/title/Wine). + +*In this repo:* no, same evidence as esync (scripts/setup-prefix.sh:56). +Also superseded: ntsync achieves the same in-kernel waits with exact NT +semantics, which futexes cannot express for wait-for-all +(https://www.techtimes.com/articles/321677/20260727/freebsd-builds-bsd-native-ntsync-driver-launches-amd-rocm-compute-port.htm). + +*Ableton applicability:* none. Skip. + +## ntsync + +*What it does:* a Linux kernel driver (also by Figura) exposing `/dev/ntsync` +with ioctls that implement NT semaphores, mutexes, events, and +any/all-waits in the kernel, with exact Windows semantics including +alertable waits via an alert event +(https://docs.kernel.org/userspace-api/ntsync.html). Wine's `ntdll` performs +waits in-process through the device instead of calling wineserver. + +*Upstream status (verified, August 2026):* + +- Kernel: an incomplete first cut merged in Linux 6.10, marked broken + (https://www.phoronix.com/news/Linux-6.10-Merging-NTSYNC); the completed, + usable driver shipped in Linux 6.14 (released 2025-03-24) + (https://www.phoronix.com/news/Linux-6.14-NTSYNC-Driver-Ready, + https://kernelnewbies.org/Linux_6.14). This repo's own check script says + the same in shorthand: "no /dev/ntsync (kernel < 6.14?)" + (scripts/check-ntsync.sh:38). +- Wine: initial ntsync support merged upstream in Wine 10.15 + (https://www.phoronix.com/news/Wine-10.15-With-NTSYNC); first stable + release with it is Wine 11.0, January 2026 + (https://news.tuxmachines.org/n/2026/01/13/Wine_11_Officially_Released_with_NTSync_Support_Vulkan_H_264_De.shtml). + This repo's base is Wine 11.13 (patches/BASE.txt:4-5), so ntsync is + upstream code here, not a Proton port. +- Proton: GE-Proton enabled ntsync by default in release 10-10, July 2025 + (https://www.gamingonlinux.com/2025/07/ge-proton-10-10-brings-tweaks-for-warframe-darksiders-mortal-kombat-1-and-ntsync-enabled-by-default/). + Valve's official Proton 11.0 beta (April 2026) rebased on Wine 11 and + added ntsync + (https://wccftech.com/valve-quietly-rebased-proton-on-wine-11-and-linux-gaming-just-got-windows-level-frame-pacing/); + Proton 11.0-1 stable followed on 2026-07-08 + (https://www.gamingonlinux.com/2026/07/proton-11-0-1-officially-released-to-expand-windows-games-on-steamos-linux/, + https://github.com/ValveSoftware/Proton/wiki/Changelog). +- Unverified: community guides claim upstream Wine ≥ 10.15 also gained an + eventfd-based in-process fallback for kernels without `/dev/ntsync` + (https://github.com/AdelKS/LinuxGamingGuide). This repo's own measurements + show its fallback build still crossed wineserver for every wait + (notes/ABLETON-WINE-NTSYNC-REGRESSION.md:38-46), but that build lacked + the ntsync header at configure time, which may gate the fallback too. + Not confirmed either way. + +*In this repo:* yes, fully shipped and regression-guarded. + +| Layer | Evidence | +|---|---| +| Vendored UAPI header (kernels ≥ 6.14) | vendor/ntsync-uapi/linux/ntsync.h:43-57; SHA-256 pinned, checked in build.sh:25 | +| Container build injects header | Containerfile:94-98 | +| Build fails if configure misses it | scripts/container-build.sh:104-125 (`HAVE_LINUX_NTSYNC_H`, both runtime halves) | +| Installed-runtime verification | scripts/check-ntsync.sh:31-38 (static gate), :68-71 (server must open `/dev/ntsync` when it exists) | +| Semantics + throughput probe | beta/tester-kit/probes/src/ntsyncprobe.c:1-10; runs A–D table in notes/ABLETON-WINE-NTSYNC-REGRESSION.md:38-43 | +| Regression history | builds 2026-07-12/14 silently lost ntsync; fixed 2026.07.17.1 (notes/ABLETON-WINE-NTSYNC-REGRESSION.md:1-6) | + +One host-side gap remains: the driver needs `/dev/ntsync` to exist, which +requires kernel ≥ 6.14 *and* the module loaded. Distributions are moving to +load it by default (Fedora 44 change proposal: +https://discussion.fedoraproject.org/t/f44-change-proposal-enable-ntsync-kernel-module-for-all-users-system-wide/161786), +but this repo only checks for the device; nothing helps a user whose kernel +has the module unloaded. + +*Ableton applicability:* this is the single most valuable technique on this +list, and it is already in. Live runs many worker threads with many short +waits at audio-period rates (a 256-frame buffer at 48 kHz is a 5.3 ms +cycle). The repo's probe measured event ping-pong rising from ~75k to +327–392k round trips/s and semaphore churn from ~64k to 3.3–3.6M pairs/s +with ntsync active (notes/ABLETON-WINE-NTSYNC-REGRESSION.md:38-43). Lower +per-wait latency and lower wineserver load directly protect audio +deadlines. Remaining limit: alertable sleeps/APC delivery still use +wineserver (notes/ABLETON-WINE-APC-COALESCING.md:14-18). + +## WINE_CPU_TOPOLOGY and core parking + +*What it does:* `WINE_CPU_TOPOLOGY=N:cpu,cpu,...` is a Proton-side Wine +patch that caps and remaps the logical processors reported to the Windows +app — used to fix games that break on high core counts and to pin games +onto specific cores (e.g. the V-Cache CCD on Ryzen X3D parts) +(https://github.com/ValveSoftware/Proton/issues/7719, +https://github.com/CachyOS/proton-cachyos/issues/178). Proton ships +per-game default CPU limits driven by this variable. It is not upstream +Wine. "Core parking" in this context is not a Wine feature at all: it is +Feral GameMode parking non-cache cores on hybrid CPUs so the game stays on +the fast ones (https://github.com/ValveSoftware/Proton/issues/8075; +GameMode project: https://github.com/FeralInteractive/gamemode). + +*In this repo:* groundwork only. The launcher computes a sensible value — +cap 8 CPUs, honor `taskset`/cgroup restrictions, user override wins — and +exports it (scripts/ableton-live:75-108), but the code is explicit: "Inert +on this runtime until the patched ntdll/wineserver consumer lands; exported +as groundwork only" (scripts/ableton-live:78-79). The consumer patch is +missing. + +*Ableton applicability:* medium. Live scales its audio worker pool to the +reported CPU count; capping at 8 avoids diminishing-returns contention on +many-core hosts, and an affinity-aware variant could keep Live off E-cores +or the non-V-Cache CCD. Unlike a game, Live under PipeWire can also be +constrained correctly from the host side with `taskset`, so this is a +convenience and correctness-of-reported-count play, not the only lever. + +## Scheduler, realtime priority, and rtkit + +*What Proton does:* very little itself. Proton relies on the host: Feral +GameMode (governor switching, renicing, core parking) and, increasingly, +sched_ext schedulers (Linux 6.12+, https://docs.kernel.org/scheduler/sched-ext.html) +in gaming distributions. Wine and wineserver have no rtkit integration; +rtkit is how PipeWire, not Wine, gets realtime priority for its data +threads without PAM limits (https://docs.pipewire.org/page_module_rt.html). + +*In this repo:* a deliberate, different design. The launcher starts Wine +under `SCHED_RR` priority 10 when `chrt -r 10 true` succeeds +(scripts/ableton-live:780-783); PAM limits grant rtprio 95 +(scripts/setup-realtime.sh:74); PipeASIO separately requests SCHED_FIFO 15 +for its data loop (notes/ABLETON-WINE-RT-SCHEDULING.md:1-7). Boosting +wineserver itself with `chrt -f -p 95` was considered and deliberately left +out — it needs root per launch, and raising a single-threaded server above +its callers can invert the contention it means to fix +(scripts/setup-realtime.sh:23-25). The known open risk: Live's realtime +threads make synchronous calls into a `SCHED_OTHER` wineserver, a classic +priority-inversion shape (notes/ABLETON-WINE-RT-SCHEDULING.md:39-41), and +launcher-wide RR on low-core machines is unmeasured +(notes/ABLETON-WINE-RT-SCHEDULING.md:29-42). + +*Ableton applicability:* high, and already partially exploited. ntsync +shrinks the wineserver traffic that made the inversion frequent, but the +APC path keeps it alive. The untested A/B (wineserver priority and CPU +affinity) is the cheapest remaining scheduler experiment; the repo already +has the A/B harness (`ABLETON_RT=off`, scripts/bench-run.sh, +notes/ABLETON-WINE-RT-SCHEDULING.md:43-81). + +## Per-app environment variables that matter for CPU + +| Variable | What it does | Where it stands here | +|---|---|---| +| `WINE_CPU_TOPOLOGY` | Cap/remap CPUs reported to the app (Proton patch) | Exported by launcher, consumer missing (scripts/ableton-live:75-108) | +| `WINEDEBUG=-all` | Cuts logging overhead; fixme spam stalled Live's UI thread | Already set (scripts/ableton-live:16-17) | +| `WINEESYNC` / `WINEFSYNC` | Toggle esync/fsync in builds that have them | Deliberately unset; N/A on this runtime (scripts/setup-prefix.sh:56) | +| `PROTON_NO_ESYNC` / `PROTON_NO_FSYNC` | Proton launch-time sync overrides | Proton-only; irrelevant outside Steam (https://github.com/ValveSoftware/Proton) | +| `WINE_D3D_CONFIG=csmt=0x1` | WineD3D command-stream threading | Already set (scripts/ableton-live:18); GPU-side, not CPU-sync | + +No Proton CPU variable other than `WINE_CPU_TOPOLOGY` has meaning for this +repo; the rest of Proton's per-game env surface is GPU/DXVK territory. + +## Wineserver replacement or rewrite efforts + +No credible full wineserver replacement or rewrite exists as of August +2026; this is a search-based negative finding, not a certainty. What exists +is a decade-long strategy of *shrinking* wineserver's role: the 2008 +shared-memory mutex discussion on wine-devel never landed +(https://list.winehq.org/hyperkitty/list/wine-devel@list.winehq.org/thread/UYWGMILMVYVQKUREZ4Q7ATYVRFC2YIZU/); +wine-tkg's "fastsync" (shared-memory sync via the out-of-tree "winesync" +module) was the direct predecessor of ntsync +(https://github.com/Frogging-Family/wine-tkg-git/issues/936) and is now +obsolete; community build sets are dropping their esync/fsync patches in +favor of upstream ntsync (https://github.com/NelloKudo/WineBuilder/releases). +Valve's and CodeWeavers' engineering goes into ntsync and into moving +functionality into per-process libraries (win32u, ntdll), not into a new +server. For this project, "replace wineserver" is not an option; "make the +remaining wineserver traffic cheaper or rarer" is. + +## What transfers to a real-time audio DAW + +Games and DAWs differ in one way that matters: a game can drop a frame; a +DAW cannot miss a buffer. The sync techniques transfer directly because +Live's thread pattern — many workers, many short waits, hard periodic +deadlines — is the pattern ntsync was benchmarked on, and this repo already +has the wins and the guards. The remaining moonshot surface is the traffic +ntsync does not cover (alertable waits, APC delivery), the priority +relationship between Live's RT threads and wineserver, and CPU-count/affinity +reporting. Proton's esync/fsync era is closed for this project; nothing +there is worth resurrecting. + +## Key opportunities + +1. **Close the `/dev/ntsync` host gap** — detect a kernel ≥ 6.14 with the + ntsync module unloaded and tell the user exactly how to load it (or ship + a `modprobe.d`/udev drop-in via setup scripts). Impact: high (users on + qualifying kernels silently lose 4–50x sync throughput otherwise). + Effort: low. Evidence: scripts/check-ntsync.sh:38,68-71 checks but does + not remediate; notes/ABLETON-WINE-NTSYNC-REGRESSION.md:38-43 quantifies + the loss. +2. **Port Proton's `WINE_CPU_TOPOLOGY` consumer patch into the runtime** — + the launcher already computes and exports the value; the ntdll/wineserver + consumer is the missing half. Impact: medium (fixes worker-pool + oversizing on >8-core hosts; enables V-Cache/P-core pinning). Effort: + medium. Evidence: scripts/ableton-live:75-108, explicit "Inert … + groundwork only" at scripts/ableton-live:78-79. +3. **Run the deferred wineserver priority/affinity A/B** — test + `chrt -f` boost and/or CPU affinity for wineserver under playback load + using the existing `bench-run.sh` harness; the boost is documented as + deliberately excluded pending measurement, not as rejected. Impact: + medium (targets the Live-RT-thread vs SCHED_OTHER-wineserver inversion). + Effort: low. Evidence: scripts/setup-realtime.sh:23-25, + notes/ABLETON-WINE-RT-SCHEDULING.md:39-41,43-81. +4. **Attack the alertable-wait/APC wineserver path** — ntsync does not + cover alertable sleeps or APC delivery; Live's APC coalescing thread + burns 30–40% of a core idle, and per-APC wineserver serialization is the + current fault hypothesis under load. Impact: high. Effort: high. + Evidence: notes/ABLETON-WINE-APC-COALESCING.md:1-6,14-28. +5. **Verify the upstream non-ntsync in-process fallback and, if real, + enable it in builds** — Unverified: community sources say upstream + Wine ≥ 10.15 has an eventfd in-process fallback when `/dev/ntsync` is + absent; this repo's header-less fallback builds paid full wineserver + round trips. If the fallback is compile-gated on the same header, no + action; if not, users on kernels < 6.14 get a free win. Impact: medium. + Effort: low. Evidence: https://github.com/AdelKS/LinuxGamingGuide vs + notes/ABLETON-WINE-NTSYNC-REGRESSION.md:38-46. +6. **Publish hybrid-CPU affinity guidance** — document `taskset` / + GameMode-style core selection for Intel P/E and Ryzen X3D hosts running + Live, matching what Proton users already do per game. Impact: low to + medium. Effort: low. Evidence: + https://github.com/ValveSoftware/Proton/issues/8075; launcher already + honors cgroup cpusets (scripts/ableton-live:85-97). diff --git a/patches/SERIES.sha256 b/patches/SERIES.sha256 index 93be7163..2f3f32f1 100644 --- a/patches/SERIES.sha256 +++ b/patches/SERIES.sha256 @@ -65,3 +65,5 @@ f25d5b4c3ee71b7e9491f91c462e2b7ddaca377b8b94c7bd95df7734f0a4b563 pipeasio/0002- 0cb3ee8b263366cc6a352517ab228e1d3198a61f48fa906b19652cb4c2b9c97c pipeasio/0003-audio-correct-the-quantum-mismatch-warning-text.patch 1c29e4021184dff178914b74d07d28d4bffc2891ea8cba789f075ed9fb375af0 pipeasio/0004-accept-any-buffer-size-in-range-log-every-adjustment.patch f94c3410be18fb2677993081f47122ba06246ee768b5fcbe1993be0e9e12f44e pipeasio/0005-converge-on-a-foreign-quantum-predict-adopt-mute.patch +5edfaf6c143e206902ce31772912b2ebc2fff3b0ae8db9c1b60d6239abdd1ccc pipeasio/0006-anchor-fallback-capture-report-clock-domains.patch +6bca950dc7188358c09c89438417bebe2848fe74855326b2b9b8255b93ec56ea pipeasio/0007-give-the-follower-device-buffer-room.patch diff --git a/patches/pipeasio/0006-anchor-fallback-capture-report-clock-domains.patch b/patches/pipeasio/0006-anchor-fallback-capture-report-clock-domains.patch new file mode 100644 index 00000000..5b43e01e --- /dev/null +++ b/patches/pipeasio/0006-anchor-fallback-capture-report-clock-domains.patch @@ -0,0 +1,207 @@ +diff --git a/src/audio.c b/src/audio.c +--- a/src/audio.c ++++ b/src/audio.c +@@ -346,11 +346,20 @@ + audio_port_t **discovered; + uint32_t n_discovered; + uint32_t cap_discovered; ++ ++ /* Hardware node each direction last resolved to ([0] capture, ++ * [1] playback) and the pair already reported, for the clock-domain ++ * report. Two devices in one graph mean PipeWire resamples one to ++ * match clocks; that is normal and should be inaudible, and the report ++ * puts the clock topology in the log for when it is not (C8). */ ++ uint32_t chosen_node[2]; ++ uint32_t warned_clock_pair[2]; + }; + + struct audio_node_info + { + uint32_t id; ++ uint32_t device_id; /* PW_KEY_DEVICE_ID owner card, SPA_ID_INVALID when none */ + char *node_name; + char *display_name; + char *media_class; +@@ -443,6 +452,8 @@ + c->sample_rate = AUDIO_DEFAULT_SAMPLE_RATE; + c->buffer_size = AUDIO_DEFAULT_BUFFER_SIZE; + c->our_node_id = SPA_ID_INVALID; ++ c->chosen_node[0] = c->chosen_node[1] = SPA_ID_INVALID; ++ c->warned_clock_pair[0] = c->warned_clock_pair[1] = SPA_ID_INVALID; + #ifndef PIPEASIO_AUDIO_UNIXLIB + atomic_init(&c->rt.ready, false); + #endif +@@ -1049,6 +1060,16 @@ + return NULL; + } + ++static struct audio_node_info *audio_find_node(audio_client_t *c, uint32_t id); ++ ++/* The device (card) a node belongs to, or SPA_ID_INVALID when unknown. */ ++static uint32_t ++audio_node_device(audio_client_t *c, uint32_t node_id) ++{ ++ struct audio_node_info *n = audio_find_node(c, node_id); ++ return n ? n->device_id : SPA_ID_INVALID; ++} ++ + /* Resolve the PipeWire default node only when it has a matching port. */ + static uint32_t + audio_preferred_default_node(audio_client_t *c, uint64_t flags) +@@ -1082,6 +1103,63 @@ + return SPA_ID_INVALID; + } + ++/* What audio_get_ports resolves for FLAGS with no explicit device: the ++ * default node when it has matching ports, else the first discovered match. ++ * Kept in step with the selection loop in audio_get_ports. */ ++static uint32_t ++audio_resolve_target_node(audio_client_t *c, uint64_t flags) ++{ ++ uint32_t id = audio_preferred_default_node(c, flags); ++ if (id != SPA_ID_INVALID) ++ return id; ++ for (uint32_t i = 0; i < c->n_discovered; i++) ++ { ++ audio_port_t *p = c->discovered[i]; ++ if ((p->flags & flags) == flags) ++ return p->pw_node_id; ++ } ++ return SPA_ID_INVALID; ++} ++ ++/* Record the hardware node a direction resolved to and report, once per ++ * pair, when capture and playback sit on different devices. PipeWire ++ * matches the two clocks by resampling, which is normal and meant to be ++ * inaudible; a follower without enough headroom resyncs audibly (C8), and ++ * this line puts the clock topology in the log for that investigation. */ ++static void ++audio_report_clock_domains(audio_client_t *c, uint64_t flags, uint32_t node_id) ++{ ++ int dir = (flags & AUDIO_PORT_IS_OUTPUT) ? 0 : 1; ++ ++ c->chosen_node[dir] = node_id; ++ ++ uint32_t cap = c->chosen_node[0], pb = c->chosen_node[1]; ++ if (cap == SPA_ID_INVALID || pb == SPA_ID_INVALID) ++ return; ++ uint32_t cap_dev = audio_node_device(c, cap); ++ uint32_t pb_dev = audio_node_device(c, pb); ++ if (cap_dev == SPA_ID_INVALID || pb_dev == SPA_ID_INVALID) ++ return; /* virtual node, or gone from the registry: cannot tell */ ++ if (cap_dev == pb_dev) ++ { ++ c->warned_clock_pair[0] = c->warned_clock_pair[1] = SPA_ID_INVALID; ++ return; ++ } ++ if (c->warned_clock_pair[0] == cap && c->warned_clock_pair[1] == pb) ++ return; /* this pair is already reported */ ++ c->warned_clock_pair[0] = cap; ++ c->warned_clock_pair[1] = pb; ++ ++ struct audio_node_info *cn = audio_find_node(c, cap); ++ struct audio_node_info *pn = audio_find_node(c, pb); ++ WARN("capture \"%s\" and playback \"%s\" are different devices; PipeWire " ++ "matches their clocks by resampling (pipeasio-clock-domains). " ++ "input_device and output_device in config.ini (or " ++ "PIPEASIO_INPUT_DEVICE / PIPEASIO_OUTPUT_DEVICE) choose the " ++ "devices.\n", ++ cn ? cn->node_name : "?", pn ? pn->node_name : "?"); ++} ++ + const char ** + audio_get_ports(audio_client_t *c, const char *port_name_pattern, const char *type_name_pattern, + uint64_t flags) +@@ -1100,6 +1178,47 @@ + + /* Use one hardware node per direction to avoid async links across devices. */ + uint32_t target_node = audio_preferred_default_node(c, flags); ++ ++ /* No usable capture default: prefer a source on the playback card over ++ * the first-discovered one, so the driver's own tie-breaking never puts ++ * a second hardware clock in the graph. A default the user set is ++ * honored above; when it is other hardware, that is reported instead of ++ * overridden (recording from a device the user did not pick is worse ++ * than resampling). */ ++ if (target_node == SPA_ID_INVALID && (flags & AUDIO_PORT_IS_OUTPUT)) ++ { ++ uint64_t pb_flags = (flags & ~(uint64_t)AUDIO_PORT_IS_OUTPUT) ++ | AUDIO_PORT_IS_INPUT | AUDIO_PORT_IS_PHYSICAL; ++ uint32_t pb_dev = audio_node_device(c, audio_resolve_target_node(c, pb_flags)); ++ uint32_t first = SPA_ID_INVALID; ++ if (pb_dev != SPA_ID_INVALID) ++ { ++ uint64_t want = flags | AUDIO_PORT_IS_PHYSICAL; ++ for (uint32_t i = 0; i < c->n_discovered; i++) ++ { ++ audio_port_t *p = c->discovered[i]; ++ if ((p->flags & want) != want) ++ continue; ++ if (first == SPA_ID_INVALID) ++ first = p->pw_node_id; ++ if (audio_node_device(c, p->pw_node_id) == pb_dev) ++ { ++ target_node = p->pw_node_id; ++ break; ++ } ++ } ++ } ++ if (target_node != SPA_ID_INVALID && first != SPA_ID_INVALID && target_node != first) ++ { ++ struct audio_node_info *tn = audio_find_node(c, target_node); ++ struct audio_node_info *fn = audio_find_node(c, first); ++ WARN("no capture default: using \"%s\" on the playback card " ++ "(pipeasio-clock-domains); first discovered was \"%s\". Set " ++ "input_device in config.ini to override.\n", ++ tn ? tn->node_name : "?", fn ? fn->node_name : "?"); ++ } ++ } ++ + for (uint32_t i = 0; i < c->n_discovered; i++) + { + audio_port_t *p = c->discovered[i]; +@@ -1125,6 +1244,8 @@ + match_idx[n_match++] = i; + } + ++ audio_report_clock_domains(c, flags, target_node); ++ + /* Allocate the NULL-terminated array. We strdup each name so the caller + * does not hold pointers into the discovered cache (the registry + * callbacks free/realloc it on hotplug); free with audio_free_ports. */ +@@ -1179,6 +1300,8 @@ + return audio_get_ports(c, NULL, NULL, flags); + } + ++ audio_report_clock_domains(c, flags, target); ++ + uint32_t *idx = NULL, n = 0, cap = 0; + for (uint32_t i = 0; i < c->n_discovered; i++) + { +@@ -1636,6 +1759,7 @@ + const char *media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); + const char *description = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION); + const char *nick = spa_dict_lookup(props, PW_KEY_NODE_NICK); ++ const char *device_id_s = spa_dict_lookup(props, PW_KEY_DEVICE_ID); + if (!node_name) + return; + +@@ -1661,6 +1785,7 @@ + if (!n) + return; + n->id = id; ++ n->device_id = device_id_s ? (uint32_t)strtoul(device_id_s, NULL, 10) : SPA_ID_INVALID; + n->node_name = strdup(node_name); + n->media_class = audio_dup_or_null(media_class); + n->display_name = strdup(description ? description : (nick ? nick : node_name)); +@@ -1933,6 +2058,10 @@ + free(n); + memmove(&c->nodes[i], &c->nodes[i + 1], (c->n_nodes - i - 1) * sizeof(*c->nodes)); + c->n_nodes--; ++ if (c->chosen_node[0] == id) ++ c->chosen_node[0] = SPA_ID_INVALID; ++ if (c->chosen_node[1] == id) ++ c->chosen_node[1] = SPA_ID_INVALID; + return; + } + } diff --git a/patches/pipeasio/0007-give-the-follower-device-buffer-room.patch b/patches/pipeasio/0007-give-the-follower-device-buffer-room.patch new file mode 100644 index 00000000..dd533a09 --- /dev/null +++ b/patches/pipeasio/0007-give-the-follower-device-buffer-room.patch @@ -0,0 +1,377 @@ +diff --git a/src/audio.c b/src/audio.c +--- a/src/audio.c ++++ b/src/audio.c +@@ -70,7 +70,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + + #include + #include +@@ -269,6 +272,19 @@ + + /* Opaque types backing audio.h. */ + ++/* One of the two devices we may be adjusting (see "Follower headroom"). */ ++struct audio_hr_slot ++{ ++ uint32_t node; /* SPA_ID_INVALID = free */ ++ struct pw_proxy *proxy; ++ struct spa_hook listener; ++ char clock_name[64]; ++ int32_t cur_headroom; ++ bool probed; ++ bool raised; ++ int32_t prev; ++}; ++ + struct audio_client + { + char *name; +@@ -354,6 +370,20 @@ + * puts the clock topology in the log for when it is not (C8). */ + uint32_t chosen_node[2]; + uint32_t warned_clock_pair[2]; ++ ++ /* Extra buffer room for two-device sessions. When input and output ++ * are separate devices, one device sets the timing and PipeWire ++ * continuously adjusts the other to keep up. That adjustment is ++ * silent while the adjusted device has spare buffer room, and ++ * crackles when it runs out (C8). While such a session plays, we ++ * raise the spare room on the adjusted device; afterwards we put the ++ * old value back. Sessions on one device are never touched. */ ++ int32_t hr_target; /* frames to guarantee; -1 disables */ ++ struct spa_source *hr_timer; ++ char hr_clock_rt[64]; /* timing clock name, audio-thread-written */ ++ _Atomic uint32_t hr_clock_gen; ++ uint64_t hr_last_seen_nsec; ++ struct audio_hr_slot hr_slot[2]; + }; + + struct audio_node_info +@@ -407,6 +437,10 @@ + .process = audio_on_process, + }; + ++/* Follower headroom (definitions after audio_report_clock_domains). */ ++static void audio_hr_tick(void *data, uint64_t expirations); ++static void audio_hr_release(audio_client_t *c, int slot, bool restore); ++ + /* Core and registry event forward declarations. */ + + static void audio_on_core_done(void *userdata, uint32_t id, int seq); +@@ -454,6 +488,29 @@ + c->our_node_id = SPA_ID_INVALID; + c->chosen_node[0] = c->chosen_node[1] = SPA_ID_INVALID; + c->warned_clock_pair[0] = c->warned_clock_pair[1] = SPA_ID_INVALID; ++ c->hr_slot[0].node = c->hr_slot[1].node = SPA_ID_INVALID; ++ ++ /* How many frames of buffer room the second device of a two-device ++ * session is guaranteed. Unset: 512. "off" or "0": leave devices ++ * alone. Other values are kept between 16 and 8192, with a message ++ * when the given value is outside that range. */ ++ const char *hr_env = getenv("PIPEASIO_FOLLOWER_HEADROOM"); ++ if (!hr_env) ++ c->hr_target = 512; ++ else if (!strcmp(hr_env, "off") || !strcmp(hr_env, "0")) ++ c->hr_target = -1; ++ else ++ { ++ long v = strtol(hr_env, NULL, 10); ++ if (v < 16) ++ v = 16; ++ if (v > 8192) ++ v = 8192; ++ if (v != atol(hr_env)) ++ WARN("PIPEASIO_FOLLOWER_HEADROOM=%s is outside 16..8192; using %ld\n", hr_env, ++ v); ++ c->hr_target = (int32_t)v; ++ } + #ifndef PIPEASIO_AUDIO_UNIXLIB + atomic_init(&c->rt.ready, false); + #endif +@@ -514,6 +571,19 @@ + if (c->registry) + pw_registry_add_listener(c->registry, &c->registry_listener, &audio_registry_events, c); + ++ /* Check every two seconds whether a two-device session needs its second ++ * device given more buffer room (and undo it when the session ends). */ ++ if (c->hr_target > 0) ++ { ++ c->hr_timer = pw_loop_add_timer(pw_thread_loop_get_loop(c->loop), audio_hr_tick, c); ++ if (c->hr_timer) ++ { ++ struct timespec iv = {2, 0}; ++ pw_loop_update_timer(pw_thread_loop_get_loop(c->loop), c->hr_timer, &iv, &iv, ++ false); ++ } ++ } ++ + pw_thread_loop_unlock(c->loop); + audio_sync(c); + /* A second sync drains the "default" metadata's initial property burst: +@@ -551,6 +621,19 @@ + if (c->active) + audio_teardown_filter(c); + ++ /* Put the raised buffer room back, and deliver that change while the ++ * connection is still open. */ ++ if (c->loop && c->hr_timer) ++ { ++ pw_thread_loop_lock(c->loop); ++ audio_hr_release(c, 0, true); ++ audio_hr_release(c, 1, true); ++ pw_loop_destroy_source(pw_thread_loop_get_loop(c->loop), c->hr_timer); ++ c->hr_timer = NULL; ++ pw_thread_loop_unlock(c->loop); ++ audio_sync(c); ++ } ++ + if (c->loop) + { + pw_thread_loop_lock(c->loop); +@@ -1160,6 +1243,211 @@ + cn ? cn->node_name : "?", pn ? pn->node_name : "?"); + } + ++/* ---- Follower headroom --------------------------------------------------- ++ * When Live's input and output are separate devices, each has its own ++ * sample clock. PipeWire lets one device set the timing and continuously ++ * adjusts the other, called the follower, to keep it in step. That ++ * adjustment is silent while the follower has spare buffer room to absorb ++ * timing wobble, and produces crackles when it runs out (C8 in the crackle ++ * note). So: while a session plays across two devices, raise the ++ * follower's spare room (the ALSA property "api.alsa.headroom"), and put ++ * the old value back when the session ends or the devices change. The ++ * property can be changed on a live node through its Props parameter; ++ * verified against PipeWire 1.6.8. Sessions on one device are never ++ * touched. PIPEASIO_FOLLOWER_HEADROOM picks the amount or turns this ++ * off. */ ++ ++/* Send a device node a new buffer-room value. */ ++static void ++audio_hr_set(struct pw_proxy *proxy, int32_t frames) ++{ ++ uint8_t buf[256]; ++ struct spa_pod_builder b; ++ struct spa_pod_frame f_obj, f_struct; ++ spa_pod_builder_init(&b, buf, sizeof buf); ++ spa_pod_builder_push_object(&b, &f_obj, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); ++ spa_pod_builder_prop(&b, SPA_PROP_params, 0); ++ spa_pod_builder_push_struct(&b, &f_struct); ++ spa_pod_builder_string(&b, "api.alsa.headroom"); ++ spa_pod_builder_int(&b, frames); ++ spa_pod_builder_pop(&b, &f_struct); ++ struct spa_pod *pod = spa_pod_builder_pop(&b, &f_obj); ++ if (pod) ++ pw_node_set_param((struct pw_node *)proxy, SPA_PARAM_Props, 0, pod); ++} ++ ++/* Read a device's clock name and current buffer room out of its reply. */ ++static void ++audio_hr_on_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, ++ const struct spa_pod *param) ++{ ++ struct audio_hr_slot *s = data; ++ (void)seq; ++ (void)index; ++ (void)next; ++ if (id != SPA_PARAM_Props || !param) ++ return; ++ const struct spa_pod_prop *prop = spa_pod_find_prop(param, NULL, SPA_PROP_params); ++ if (!prop) ++ return; /* not an ALSA device: never marked probed, so never touched */ ++ struct spa_pod_parser prs; ++ struct spa_pod_frame f; ++ spa_pod_parser_pod(&prs, &prop->value); ++ if (spa_pod_parser_push_struct(&prs, &f) < 0) ++ return; ++ const char *key; ++ while (spa_pod_parser_get_string(&prs, &key) >= 0) ++ { ++ struct spa_pod *val; ++ if (spa_pod_parser_get_pod(&prs, &val) < 0) ++ break; ++ if (!strcmp(key, "clock.name") && spa_pod_is_string(val)) ++ { ++ const char *cn = NULL; ++ if (spa_pod_get_string(val, &cn) >= 0 && cn) ++ snprintf(s->clock_name, sizeof s->clock_name, "%s", cn); ++ } ++ else if (!strcmp(key, "api.alsa.headroom") && spa_pod_is_int(val)) ++ spa_pod_get_int(val, &s->cur_headroom); ++ } ++ s->probed = true; ++} ++ ++static const struct pw_node_events audio_hr_node_events = { ++ PW_VERSION_NODE_EVENTS, ++ .param = audio_hr_on_param, ++}; ++ ++/* Stop tracking one device, optionally putting its old buffer room back. */ ++static void ++audio_hr_release(audio_client_t *c, int i, bool restore) ++{ ++ struct audio_hr_slot *s = &c->hr_slot[i]; ++ if (s->node == SPA_ID_INVALID) ++ return; ++ if (s->proxy) ++ { ++ if (restore && s->raised) ++ { ++ struct audio_node_info *n = audio_find_node(c, s->node); ++ audio_hr_set(s->proxy, s->prev); ++ WARN("restored \"%s\" to %d frames of buffer room " ++ "(pipeasio-follower-headroom)\n", ++ n ? n->node_name : "?", s->prev); ++ } ++ spa_hook_remove(&s->listener); ++ pw_proxy_destroy(s->proxy); ++ } ++ memset(s, 0, sizeof *s); ++ s->node = SPA_ID_INVALID; ++} ++ ++static void ++audio_hr_tick(void *data, uint64_t expirations) ++{ ++ audio_client_t *c = data; ++ (void)expirations; ++ ++ /* A stopped stream keeps its last clock timestamp; audio is only ++ * really running when the timestamp moved since the previous tick. */ ++ uint64_t nsec = c->last_clock_nsec; ++ bool running = nsec != 0 && nsec != c->hr_last_seen_nsec; ++ c->hr_last_seen_nsec = nsec; ++ ++ uint32_t cap = c->chosen_node[0], pb = c->chosen_node[1]; ++ struct audio_node_info *cn = audio_find_node(c, cap); ++ struct audio_node_info *pn = audio_find_node(c, pb); ++ bool two = cn && pn && cn->device_id != SPA_ID_INVALID ++ && pn->device_id != SPA_ID_INVALID && cn->device_id != pn->device_id; ++ ++ uint32_t want[2] = {SPA_ID_INVALID, SPA_ID_INVALID}; ++ if (two && running && c->hr_target > 0 && c->registry) ++ { ++ want[0] = cap; ++ want[1] = pb; ++ } ++ ++ /* Let go of devices we no longer track (session ended, devices changed). */ ++ for (int i = 0; i < 2; i++) ++ { ++ struct audio_hr_slot *s = &c->hr_slot[i]; ++ if (s->node != SPA_ID_INVALID && s->node != want[0] && s->node != want[1]) ++ audio_hr_release(c, i, true); ++ } ++ if (want[0] == SPA_ID_INVALID) ++ return; ++ ++ /* Which clock is setting the timing right now (the audio thread wrote ++ * this; an odd or changed counter means the copy was half-finished). */ ++ char gclock[64]; ++ uint32_t g1 = atomic_load(&c->hr_clock_gen); ++ memcpy(gclock, c->hr_clock_rt, sizeof gclock); ++ if ((g1 & 1) || g1 != atomic_load(&c->hr_clock_gen)) ++ return; /* half-finished copy; try again next tick */ ++ gclock[sizeof gclock - 1] = '\0'; ++ if (!gclock[0]) ++ return; ++ ++ /* Start tracking both devices, and ask each once for its clock name ++ * and its current buffer room. */ ++ for (int w = 0; w < 2; w++) ++ { ++ bool have = false; ++ for (int i = 0; i < 2; i++) ++ if (c->hr_slot[i].node == want[w]) ++ have = true; ++ if (have) ++ continue; ++ for (int i = 0; i < 2; i++) ++ { ++ struct audio_hr_slot *s = &c->hr_slot[i]; ++ if (s->node != SPA_ID_INVALID) ++ continue; ++ s->proxy = pw_registry_bind(c->registry, want[w], PW_TYPE_INTERFACE_Node, ++ PW_VERSION_NODE, 0); ++ if (!s->proxy) ++ break; ++ s->node = want[w]; ++ pw_node_add_listener((struct pw_node *)s->proxy, &s->listener, ++ &audio_hr_node_events, s); ++ pw_node_enum_params((struct pw_node *)s->proxy, 0, SPA_PARAM_Props, 0, 1, ++ NULL); ++ break; ++ } ++ } ++ ++ /* Raise the device that is not setting the timing; undo the raise if ++ * that role later lands on a device we raised. */ ++ for (int i = 0; i < 2; i++) ++ { ++ struct audio_hr_slot *s = &c->hr_slot[i]; ++ if (s->node == SPA_ID_INVALID || !s->probed || !s->clock_name[0]) ++ continue; ++ bool sets_timing = !strcmp(s->clock_name, gclock); ++ if (!sets_timing && !s->raised && s->cur_headroom < c->hr_target) ++ { ++ struct audio_node_info *n = audio_find_node(c, s->node); ++ s->prev = s->cur_headroom; ++ s->raised = true; ++ audio_hr_set(s->proxy, c->hr_target); ++ WARN("two audio devices are in use, and \"%s\" follows the other " ++ "one's clock; it got %d frames of buffer room (was %d) so " ++ "the adjustment stays silent (pipeasio-follower-headroom). " ++ "PIPEASIO_FOLLOWER_HEADROOM changes or disables this.\n", ++ n ? n->node_name : "?", c->hr_target, s->prev); ++ } ++ else if (sets_timing && s->raised) ++ { ++ struct audio_node_info *n = audio_find_node(c, s->node); ++ audio_hr_set(s->proxy, s->prev); ++ s->raised = false; ++ WARN("\"%s\" now sets the timing itself; its buffer room is back " ++ "to %d frames (pipeasio-follower-headroom)\n", ++ n ? n->node_name : "?", s->prev); ++ } ++ } ++} ++ + const char ** + audio_get_ports(audio_client_t *c, const char *port_name_pattern, const char *type_name_pattern, + uint64_t flags) +@@ -1569,6 +1857,18 @@ + if (quantum) + atomic_store(&c->observed_quantum, quantum); + ++ /* Remember the name of the clock that is setting the timing (the ++ * buffer-room timer reads it). Copy only when it changes; the counter ++ * is bumped before and after the copy, so a reader that catches a ++ * half-finished copy can tell, and tries again later. */ ++ if (position && position->clock.name[0] ++ && strncmp(c->hr_clock_rt, position->clock.name, sizeof c->hr_clock_rt)) ++ { ++ atomic_fetch_add(&c->hr_clock_gen, 1); ++ memcpy(c->hr_clock_rt, position->clock.name, sizeof c->hr_clock_rt); ++ atomic_fetch_add(&c->hr_clock_gen, 1); ++ } ++ + /* bufferSwitch and PipeWire must run at the same quantum. While they + * disagree, mute: publish silence and zero the input, so the audible + * fault is silence plus one log line per episode instead of wrong-speed +@@ -2062,6 +2362,9 @@ + c->chosen_node[0] = SPA_ID_INVALID; + if (c->chosen_node[1] == id) + c->chosen_node[1] = SPA_ID_INVALID; ++ for (int j = 0; j < 2; j++) ++ if (c->hr_slot[j].node == id) ++ audio_hr_release(c, j, false); /* device gone; nothing to restore */ + return; + } + } diff --git a/scripts/build-audit.sh b/scripts/build-audit.sh index 9e754265..b8848687 100755 --- a/scripts/build-audit.sh +++ b/scripts/build-audit.sh @@ -144,6 +144,8 @@ pipeasio/0002|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-midi-timebas pipeasio/0003|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-quantum-arbitration pipeasio/0004|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-any-buffer-size pipeasio/0005|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-quantum-converge +pipeasio/0006|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-clock-domains +pipeasio/0007|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-follower-headroom ' # pipeasio's code is in the unix .so; the PE pipeasio64.dll is a codeless fake module. STAMP_ONLY=' From bc39b63ba01140afd1599d14809a1c6f0f9c12a8 Mon Sep 17 00:00:00 2001 From: Shibco Date: Fri, 7 Aug 2026 19:57:21 +0200 Subject: [PATCH 2/3] P3 F8: follower headroom limits, pipeasio audit paths, docs language pass --- TROUBLESHOOTING.md | 24 ++- notes/ABLETON-WINE-PIPEASIO-CRACKLE.md | 25 +++- .../docs/ABLETON-LIVE-TECHNICAL.md | 12 +- .../docs/ABLETON-WINE-PERFORMANCE-PLAN.md | 6 +- .../docs/AUDIO-LATENCY-ECOSYSTEM.md | 14 +- .../docs/MOONSHOT-OPPORTUNITIES.md | 2 +- .../docs/OTHER-FORKS-SURVEY.md | 28 ++-- .../docs/OWN-FORK-BUILD-AND-RUNTIME.md | 16 +- .../docs/OWN-FORK-PATCH-MAP.md | 26 ++-- .../docs/PROJECT-TECHNOLOGIES.md | 8 +- .../docs/PROTON-GRAPHICS-AND-RUNTIME.md | 22 +-- .../docs/PROTON-SYNC-AND-CPU.md | 34 ++--- patches/SERIES.sha256 | 2 +- ...give-the-follower-device-buffer-room.patch | 139 +++++++++++++----- scripts/build-audit.sh | 28 ++-- 15 files changed, 237 insertions(+), 149 deletions(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 97e61e4f..264a32e4 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -109,7 +109,7 @@ switches to that size, then audio comes back on its own. If it happens once, there is nothing to do. If it keeps happening, or audio plays too fast or too slow instead of -coming back (releases up to 2026.08.01.1 do this), clear the stuck buffer +coming back (releases up to 2026.08.04.1 do this), clear the stuck buffer size and restart Live: ```bash @@ -120,26 +120,22 @@ If you still have problems, try rebooting. If they come back after that, run `./scripts/audio-report.sh` from a repository checkout and attach the output to an issue. -## Live is slow and wineserver uses a full CPU core +## Live is slow and CPU use stays high -Without `/dev/ntsync`, every Windows synchronization wait becomes a -wineserver round trip: measured at about 45 percent of one core and 9,000 -context switches per second with Live idle -([details](notes/ABLETON-WINE-NTSYNC-REGRESSION.md)). The launcher warns at -startup when the device is missing; `ls /dev/ntsync` checks by hand. - -`ntsync` ships in Linux 6.14 and newer. On a 6.14+ kernel, load the module -and make the load permanent: +Live runs much slower without a Linux feature called ntsync. The launcher +warns at each startup while it is missing. Turn it on now and at every +boot: ```bash sudo modprobe ntsync echo ntsync | sudo tee /etc/modules-load.d/90-ableton-ntsync.conf ``` -`setup-realtime.sh` installs the same drop-in. On kernels older than 6.14, -or built without `CONFIG_NTSYNC`, move to a distribution kernel that -provides the module. Relaunch Live to verify: the warning is gone. From a -repository checkout, `./scripts/check-ntsync.sh` runs the full probe. +Restart Live. The startup warning no longer appears. + +If the first command reports that the module does not exist, your kernel is +too old. Install your distribution's newest kernel (6.14 or newer) and run +the commands again. ## Audio latency remains high diff --git a/notes/ABLETON-WINE-PIPEASIO-CRACKLE.md b/notes/ABLETON-WINE-PIPEASIO-CRACKLE.md index c4af52e4..ea48a288 100644 --- a/notes/ABLETON-WINE-PIPEASIO-CRACKLE.md +++ b/notes/ABLETON-WINE-PIPEASIO-CRACKLE.md @@ -146,6 +146,11 @@ across every stream in the graph, which no buffer size corrects. The `spa.alsa: hw:M2p: follower ... resync` line in #49 is that event. C8 governs whether the follower copes. +*Corrected 2026-08-02: two devices in one graph are a supported +configuration and the resampling is silent when the follower has enough +buffer room. The crackle is C8's headroom shortfall, not the two-device +graph itself. See the status addendum below.* + ### Working baseline Taken on the build machine on 2026-07-26 with Live open: @@ -158,7 +163,7 @@ pw-top: R 74 256 48000 alsa_input...analog-stereo (driver) R 106 256 48000 = Ableton Live 12 Suite BUSY 270us B/Q 0.05 ``` -Note two things even here. The capture device drives the graph, which is C9 +Even this baseline shows two things. The capture device drives the graph, which is C9 in its harmless form because both devices are the same card. And `clock.quantum` reads 512 while the graph runs at 256, so the settings metadata reports the configured default rather than the running quantum. @@ -371,7 +376,7 @@ it was written 2026-07-26. State of the plan as of this addendum: telemetry running throughout; clearing the pin left the graph stable at the re-established node force. G1 is answered yes by the observed rebuild: Live 12.4.3 services kAsioResetRequest while running. C1 is - closed. One instrumentation note: the driver's unix-side messages reach + closed. Instrumentation: the driver's unix-side messages reach the session log with their own `[pipeasio]` prefix, and its wine TRACE class did not surface even with `trace+asio`, so the G1 log line is invisible in the field; the behavioural answer stands. @@ -397,8 +402,20 @@ it was written 2026-07-26. State of the plan as of this addendum: the node's Props parameter), logs the change, and puts the old value back when the session ends, the devices change, or that device starts setting the graph's timing itself. Single-device sessions are never - touched. `PIPEASIO_FOLLOWER_HEADROOM` sets a different frame count or - turns this off. The live property path is verified against PipeWire + touched. One limit is inherent to the approach. The restore runs only + on the driver's normal paths, so if the process dies without a clean + exit (a crash or a kill), the added room stays on the device node + until PipeWire recreates it, and every client of that device keeps + the added latency meanwhile. A later session does not undo it + either: the driver probes the raised value, sees nothing to raise, + and never records the original. On PipeWire 1.5.85 the Props + enumeration yields multiple objects behind the audio adapter, and + all of them must be enumerated, otherwise `clock.name` stays empty + and the feature never engages. `PIPEASIO_FOLLOWER_HEADROOM` accepts + a frame count from 16 to 8192; smaller positive values rise to 16 and + larger values come down to 8192. `off`, `0`, a value below 1, or text + that is not a number turns this off. The live property path is verified + against PipeWire 1.6.8 on the build machine. Open: a listening run on a real rig (USB microphone plus a separate USB interface, no crackle over a long session), the oldest supported PipeWire (the driver does nothing when diff --git a/notes/performance-moonshot/docs/ABLETON-LIVE-TECHNICAL.md b/notes/performance-moonshot/docs/ABLETON-LIVE-TECHNICAL.md index 4c407956..6883641d 100644 --- a/notes/performance-moonshot/docs/ABLETON-LIVE-TECHNICAL.md +++ b/notes/performance-moonshot/docs/ABLETON-LIVE-TECHNICAL.md @@ -50,7 +50,7 @@ Implication for Wine: - The audio thread's deadline is a few milliseconds (256 frames at 48 kHz is about 5.3 ms). Every synchronous wineserver call on an audio-pool thread is a deadline risk; ntsync coverage and any remaining server round trips in the APC path are the highest-leverage audio-stability targets (`notes/ABLETON-WINE-APC-COALESCING.md:31-47`). - Unverified: whether Wine maps Live's Windows thread-priority requests to Linux scheduling at all, and how that interacts with the launcher's blanket `SCHED_RR`. The scheduling note lists untested hypotheses: Linux's 950 ms/s realtime throttle, all inherited threads sharing RR 10, and Live's realtime threads outranking the `SCHED_OTHER` wineserver they make synchronous calls to (`notes/ABLETON-WINE-RT-SCHEDULING.md:30-42`). - Buffer size is the user's latency knob end to end: Live buffer, PipeASIO `PIPEASIO_PREFERRED_BUFFERSIZE`, PipeWire quantum. The chain already works (force-quantum follows the ASIO buffer), so Wine work here is validation, not plumbing (`notes/ABLETON-WINE-PIPEASIO.md:87-96`). -- Export timing (tempo ramps) is clock-independent — export reads no audio device clock — so any remaining render difference points at math or engine evaluation order, not at scheduling (`notes/FINDINGS-TEMPO-RAMP-2026-07-31.md:36-41`). +- Export timing (tempo ramps) is clock-independent (export reads no audio device clock), so any remaining render difference points at math or engine evaluation order, not at scheduling (`notes/FINDINGS-TEMPO-RAMP-2026-07-31.md:36-41`). ## Plugin hosting @@ -95,7 +95,7 @@ How Live draws: Implication for Wine: -- UI rendering is GPU-bound work travelling over Wine's d2d1/wined3d/dcomp stack — the reason this fork's base is giang17's d2d1-dcomp branch (`patches/BASE.txt:3-6`). The remaining known costs are the copy path for `WS_POPUP` and `WS_CHILD` windows and any frame that fails the 0058 agreement gate (`notes/ABLETON-WINE-GPU-RENDERER.md:78-86`, `notes/ABLETON-WINE-GPU-RENDERER.md:161-165`). +- UI rendering is GPU-bound work travelling over Wine's d2d1/wined3d/dcomp stack, the reason this fork's base is giang17's d2d1-dcomp branch (`patches/BASE.txt:3-6`). The remaining known costs are the copy path for `WS_POPUP` and `WS_CHILD` windows and any frame that fails the 0058 agreement gate (`notes/ABLETON-WINE-GPU-RENDERER.md:78-86`, `notes/ABLETON-WINE-GPU-RENDERER.md:161-165`). - WebView2 is a second compositor inside the process, in software mode, versioned outside this project's control. An Evergreen update can regress the Learn and Splice views without any change in Wine or Live; the launcher records the WebView2 version for this reason (`notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md:186-189`). - DPI agreement between threads is a recurring root cause (resize loop, present-path black band). Patches 0023 and 0059 bracket rect queries in the target window's DPI context; any new present or resize code needs the same discipline (`notes/ABLETON-WINE-GPU-RENDERER.md:133-156`, `notes/ABLETON-WINE-PIANOTEQ-DPI-GHOST-BUG.md:31-37`). @@ -110,13 +110,13 @@ How Max for Live works here: - M4L device windows come and go as Live children when tracks are selected. That visibility change flipped Wine's whole client surface between attached and offscreen-composited paths, flashing the window black; patch 0062 keeps the Live class on the offscreen path (`notes/ABLETON-WINE-M4L-SELECTION-FLICKER.md:11-22`, `notes/ABLETON-WINE-M4L-SELECTION-FLICKER.md:41-58`). - An open M4L editor window adds audio latency; Ableton recommends closing editors (https://help.ableton.com/hc/en-us/articles/209072289-How-to-reduce-latency). - Standalone Max 9 also runs under this Wine. The project ships a `max9` desktop entry and registers the `c74max:` URL scheme and the `.amxd` MIME type (`desktop/max9.desktop.in:1-11`, `desktop/wine-protocol-c74max.desktop.in:1-8`). -- Threading trivia with operational impact: Live names 46 threads `MainThread`; identifying threads by name misled an earlier investigation (`notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:225-228`). +- Threading facts with operational impact: Live names 46 threads `MainThread`; identifying threads by name misled an earlier investigation (`notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:225-228`). Implication for Wine: -- Max inherits every Wine graphics and font defect Live hits, plus its own. The font deadlock shows a class of bug where Wine's honesty (reporting font failure that Windows hides) turns a Windows-latent defect into a Live hang. Similar Windows-lax behaviours elsewhere (font substitution, GDI mapper, EnumFontFamilies output) are worth auditing before chasing M4L reports (`notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:182-197`). +- Max inherits every Wine graphics and font defect Live hits, plus its own. The font deadlock shows a class of bug where Wine reports a font failure that Windows hides, turning a Windows-latent defect into a Live hang. Audit the similar Windows-lax behaviours elsewhere (font substitution, GDI mapper, EnumFontFamilies output) before chasing M4L reports (`notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:182-197`). - The `WaitForVBlank` semi-stub means M4L devices with continuous redraw (meters, jsui) are paced by `Sleep(16)`, not by real vblank. Unverified: whether this costs UI smoothness or CPU in normal use; it is the known pacing point for all Max rendering (`notes/FINDINGS-M4L-CARBON-REGULATOR-DEADLOCK-2026-07-29.md:86-88`). -- Known upstream failure mode to keep in mind: Live freezing at "Starting Max..." is reported on other Wine builds (https://github.com/Frogging-Family/wine-tkg-git/issues/1226); this fork's font fix addresses one specific trigger, not the general class. +- Known upstream failure mode: Live freezing at "Starting Max..." is reported on other Wine builds (https://github.com/Frogging-Family/wine-tkg-git/issues/1226); this fork's font fix addresses one specific trigger, not the general class. ## Link @@ -196,7 +196,7 @@ Classes observed on this stack, with root cause and status. Signatures make them Reported upstream and elsewhere, for pattern matching: -- wineserver using a full CPU core with Live 10, fixed by a patch (Wine bug 47281, https://bugs.winehq.org/show_bug.cgi?id=47281 — page content not re-verified; Bugzilla currently sits behind an anti-bot wall, summary per search index). +- wineserver using a full CPU core with Live 10, fixed by a patch (Wine bug 47281, https://bugs.winehq.org/show_bug.cgi?id=47281; page content not re-verified; Bugzilla currently sits behind an anti-bot wall, summary per search index). - Live 12 severe graphical issues on default options; `-_ForceGdiBackend` workaround left Max devices' UIs frozen (Wine bug 57260, https://list.winehq.org/archives/list/wine-bugs@list.winehq.org/thread/DUN3WQJ4TUSHKDA37BVL3PELHXZD6BRP/). - Live 12 crashes opening sets saved in older versions, and M4L freezes (Wine bugs 56540 and 56537, https://list.winehq.org/hyperkitty/list/wine-bugs@list.winehq.org/thread/O2SD7WTZRJJPOQRWUBMG6CGACLCE6FPQ/). - Freeze at "Starting Max..." on wine-tkg (https://github.com/Frogging-Family/wine-tkg-git/issues/1226). diff --git a/notes/performance-moonshot/docs/ABLETON-WINE-PERFORMANCE-PLAN.md b/notes/performance-moonshot/docs/ABLETON-WINE-PERFORMANCE-PLAN.md index 025bd663..c3923914 100644 --- a/notes/performance-moonshot/docs/ABLETON-WINE-PERFORMANCE-PLAN.md +++ b/notes/performance-moonshot/docs/ABLETON-WINE-PERFORMANCE-PLAN.md @@ -292,7 +292,7 @@ Pass gates: ### 2. Shorten queued audio wake-ups without changing their order The local test that disabled batching for Windows queued asynchronous procedure -calls (`-DontCombineAPCs`) removed a 30–40% idle thread but made playback slow +calls (`-DontCombineAPCs`) removed a 30-40% idle thread but made playback slow and broken. The recorded analysis concludes that uncombined queued work likely increased traffic through the single-threaded wineserver and delayed PipeASIO. The option was correctly removed. See @@ -326,7 +326,7 @@ performance-core-only is faster for every set. It can reduce parallel capacity. ### 4. Remove recurring graphics repair work The current graphics work already produced the largest measured local gain: -enabling Live's D3D11 renderer reduced idle CPU from about 59% to 1–2%, and +enabling Live's D3D11 renderer reduced idle CPU from about 59% to 1-2%, and direct OpenGL presentation removed about 650 MB/s of full-window display traffic. These results are recorded in [`notes/ABLETON-WINE-GPU-RENDERER.md`](ABLETON-WINE-GPU-RENDERER.md). @@ -459,7 +459,7 @@ total audio-engine load, when judging these builds. path and retain a correct fallback. - Do not use allocator replacements, huge pages, `-O3`, or native CPU tuning without a measured Wine hot path and a stability result. -- Do not set 0.2–0.4 ms audio periods as a default. They reduce the time +- Do not set 0.2-0.4 ms audio periods as a default. They reduce the time available to recover from ordinary scheduling delay. - Do not disable runtime checks for speed. A media path can depend on those checks for correct fault handling. diff --git a/notes/performance-moonshot/docs/AUDIO-LATENCY-ECOSYSTEM.md b/notes/performance-moonshot/docs/AUDIO-LATENCY-ECOSYSTEM.md index 30127040..528693f6 100644 --- a/notes/performance-moonshot/docs/AUDIO-LATENCY-ECOSYSTEM.md +++ b/notes/performance-moonshot/docs/AUDIO-LATENCY-ECOSYSTEM.md @@ -32,7 +32,7 @@ WineASIO is the original ASIO-to-JACK driver for Wine. The upstream repository i This project replaced WineASIO with PipeASIO because WineASIO summed inputs to mono and showed high latency under load (issue #4), and because WineASIO can only see ports that PipeWire's JACK layer exposes (issue #5) (`notes/ABLETON-WINE-PIPEASIO.md:9-13`). A second, structural reason appears in the hotplug note: when a device disappears, PipeWire removes the JACK links to WineASIO and does not restore them on return, so this project had to run `jacklinkd` to recreate links (`notes/ABLETON-WINE-AUDIO-HOTPLUG.md:12-19`). WineASIO's regression risk is also visible upstream: version 1.2.0 (September 2023) exists only to "fix compatibility with Wine > 8" (https://github.com/wineasio/wineasio/). No WineASIO fork adds native PipeWire support; PipeASIO is the native-PipeWire line. Unverified: this survey did not re-audit every WineASIO fork on GitHub as of August 2026. -Relevance to the moonshot: the JACK detour is already gone. The remaining WineASIO-era gap is evidence. The two comparisons the evaluation note still lists as missing — reproducing issue #4 under WineASIO on the same interface, and comparing WineASIO and PipeASIO xruns under identical load — are still open (`notes/ABLETON-WINE-PIPEASIO.md:87-95`). +Relevance to the moonshot: the JACK detour is already gone. The remaining WineASIO-era gap is evidence. The two comparisons the evaluation note still lists as missing (reproducing issue #4 under WineASIO on the same interface, and comparing WineASIO and PipeASIO xruns under identical load) are still open (`notes/ABLETON-WINE-PIPEASIO.md:87-95`). ## Upstream Wine audio drivers: winepulse.drv and winealsa.drv @@ -40,7 +40,7 @@ Live uses ASIO, so these drivers matter only as a fallback path and as context f `winepulse.drv` implements the Windows WASAPI/MMDevice API over PulseAudio's API (which PipeWire serves through `pipewire-pulse`). In upstream Wine's `dlls/winepulse.drv/pulse.c`, the stream buffer target length is three periods (`attr.tlength = period_bytes * 3`), and the default device period is probed from the server's minimum request size times ten (https://raw.githubusercontent.com/wine-mirror/wine/master/dlls/winepulse.drv/pulse.c, as fetched August 2026). Three periods of buffering plus the server's own buffering puts the winepulse path far above ASIO latency. The same source contains no environment-variable latency override. Separate crash-bug evidence in this repo: with WirePlumber stopped, PipeWire exposes only `auto_null` and winepulse can block while Live enumerates endpoints (`notes/ABLETON-WINE-AUDIO-CRASH-BUG.md:22-24`). -`winealsa.drv` talks to ALSA directly and can beat winepulse on latency, but it bypasses PipeWire, so it grabs the device exclusively and cannot share it with other applications — exactly what a desktop DAW setup must avoid. Community references still recommend JACK/WineASIO-style paths over both drivers for Live-class workloads (https://askubuntu.com/questions/1292282/high-latency-and-poor-sound-quality-when-running-ableton-live-daw-using-wine). +`winealsa.drv` talks to ALSA directly and can beat winepulse on latency, but it bypasses PipeWire, so it grabs the device exclusively and cannot share it with other applications, exactly what a desktop DAW setup must avoid. Community references still recommend JACK/WineASIO-style paths over both drivers for Live-class workloads (https://askubuntu.com/questions/1292282/high-latency-and-poor-sound-quality-when-running-ableton-live-daw-using-wine). Relevance to the moonshot: none of these drivers should ever serve Live's engine. The one actionable item is keeping the mmdevapi enumeration path (used by winepulse) from stalling Live's startup, which patch 0021 already addresses (`notes/ABLETON-WINE-AUDIO-CRASH-BUG.md:33-36`). @@ -71,7 +71,7 @@ PipeWire's latency is `quantum / rate`. The upstream default quantum is 1024; at | `api.alsa.disable-batch`, `api.alsa.headroom`, `api.alsa.period-size`, `api.alsa.period-num` | ALSA device buffering in WirePlumber rules | same | | `PIPEWIRE_LATENCY=256/48000` | Per-client latency request (frames/rate) | https://juij.fun/static/Wine%20%26%20Proton%20%E5%85%BC%E5%AE%B9%E5%B1%82%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E5%8F%8A%E5%90%AF%E5%8A%A8%E9%A1%B9%E5%8F%82%E6%95%B0%E5%8F%82%E8%80%83 | -Two caveats from practice. USB interfaces often need 512 frames as the stable minimum, while built-in HDA can reach 256 and good PCIe cards 64 (https://oneuptime.com/blog/post/2026-03-02-configure-pipewire-low-latency-audio-ubuntu/view). And a 2023 community test found PipeWire stable at 256 samples (about 18 ms round trip) but producing xruns when pushed lower — dated, but a reminder that graph-wide stability, not the driver, is the usual binding constraint (https://linuxcreative.com/articles/pipewire-the-next-big-thing-in-linux-audio-production/). +Two caveats from practice. USB interfaces often need 512 frames as the stable minimum, while built-in HDA can reach 256 and good PCIe cards 64 (https://oneuptime.com/blog/post/2026-03-02-configure-pipewire-low-latency-audio-ubuntu/view). And a 2023 community test found PipeWire stable at 256 samples (about 18 ms round trip) but producing xruns when pushed lower: dated, but it shows that graph-wide stability, not the driver, is the usual binding constraint (https://linuxcreative.com/articles/pipewire-the-next-big-thing-in-linux-audio-production/). Interaction with this repo: PipeASIO already forces the graph quantum to the ASIO buffer on PipeWire 1.6+ (`notes/ABLETON-WINE-PIPEASIO.md:20`), but the shipped runtime only requires host PipeWire 0.3.56 (`notes/ABLETON-WINE-PIPEASIO.md:46-48`), so hosts on older PipeWire get no quantum matching and the graph runs at its default 1024 unless the user sets it. Unverified: the fallback behavior on those hosts has not been measured by this project. @@ -89,11 +89,11 @@ rtkit (RealtimeKit) is a D-Bus service that grants realtime scheduling to unpriv This creates two failure modes relevant here. First, a user without `rtprio` limits falls back to rtkit, whose priority ceiling of 20 sits below the priorities JACK and PipeWire documentation assume; the pro-audio consensus is to prefer `RLIMIT_RTPRIO` through `limits.conf` (or the `realtime-privileges` package) over rtkit (https://github.com/rerdavies/pipedal/discussions/99). Second, yabridge documents the 200 ms RTTIME cap as a concrete cause of warnings and throttled audio threads, and notes GNOME 45+ forces this path (https://github.com/robbert-vdh/yabridge). -This repo's launcher needs `rtprio` to succeed at its `chrt -r 10` probe (`scripts/ableton-live:780-782`), and `scripts/setup-realtime.sh` installs that permission; the script deliberately leaves out a wineserver `chrt -f -p 95` boost because it needs root (`scripts/setup-realtime.sh:13-23`). The moonshot decision to make is whether the priority ladder — PipeWire data threads (rtkit/rt.prio), Live under RR 10, PipeASIO at FIFO 15, wineserver at `SCHED_OTHER` — is the right order. Today the component Live's realtime threads block on most, wineserver, has the lowest priority. That is a classic priority-inversion shape, already listed as an unconfirmed hypothesis in the RT note (`notes/ABLETON-WINE-RT-SCHEDULING.md:38-41`). +This repo's launcher needs `rtprio` to succeed at its `chrt -r 10` probe (`scripts/ableton-live:780-782`), and `scripts/setup-realtime.sh` installs that permission; the script deliberately leaves out a wineserver `chrt -f -p 95` boost because it needs root (`scripts/setup-realtime.sh:13-23`). The moonshot decision to make is whether the priority ladder (PipeWire data threads (rtkit/rt.prio), Live under RR 10, PipeASIO at FIFO 15, wineserver at `SCHED_OTHER`) is the right order. Today the component Live's realtime threads block on most, wineserver, has the lowest priority. That is a classic priority-inversion shape, already listed as an unconfirmed hypothesis in the RT note (`notes/ABLETON-WINE-RT-SCHEDULING.md:38-41`). ## JACK versus PipeWire for this use case -The Arch guide calls JACK the mature, pro-audio-designed server and PipeWire "a sufficient server for most of the use cases," while noting open doubt about PipeWire for professional work (https://wiki.archlinux.org/title/Professional_audio). The strongest 2026 measurement found is the SuperCollider scsynth experiment: a native PipeWire backend showed 40-50% lower CPU than the JACK-on-PipeWire shim at equal DSP load, but pinning both to the same cores erased the gap — the kernel had been placing the shim's audio thread on an efficiency core and the native client's thread on a performance core of a hybrid CPU (https://scsynth.org/t/experimental-native-pipewire-audio-backend-for-scsynth-linux-feedback-wanted/13292). Two conclusions transfer directly. The JACK compatibility layer itself adds negligible per-callback work. And thread placement, especially on hybrid Intel/AMD CPUs, can dominate every other tuning decision. +The Arch guide calls JACK the mature, pro-audio-designed server and PipeWire "a sufficient server for most of the use cases," while noting open doubt about PipeWire for professional work (https://wiki.archlinux.org/title/Professional_audio). The strongest 2026 measurement found is the SuperCollider scsynth experiment: a native PipeWire backend showed 40-50% lower CPU than the JACK-on-PipeWire shim at equal DSP load, but pinning both to the same cores erased the gap: the kernel had been placing the shim's audio thread on an efficiency core and the native client's thread on a performance core of a hybrid CPU (https://scsynth.org/t/experimental-native-pipewire-audio-backend-for-scsynth-linux-feedback-wanted/13292). Two conclusions transfer directly. The JACK compatibility layer itself adds negligible per-callback work. And thread placement, especially on hybrid Intel/AMD CPUs, can dominate every other tuning decision. For this project the JACK-versus-PipeWire question is settled: PipeASIO made Live a native PipeWire client, which also fixed hotplug and port-visibility issues JACK could not (`notes/ABLETON-WINE-PIPEASIO.md:9-21`, `notes/ABLETON-WINE-AUDIO-HOTPLUG.md:12-19`). The transferable lesson from the scsynth data is different: verify which cores Live's and PipeASIO's threads land on, and consider pinning, before touching kernels. @@ -107,9 +107,9 @@ Native DAWs show what the same hardware can do without Wine in the path. | REAPER native Linux | Community reports of 1.4/2.9 ms latency settings in REAPER's own audio device settings, which operate independently of PipeWire | https://forum.cockos.com/showthread.php?p=2628855, https://forum.cockos.com/showthread.php?p=2879612 | | Ardour | Uses the same JACK/PipeWire-JACK stack; per-application quantum/rate settings let each program run its intended buffer without forcing the whole graph | https://discourse.ardour.org/t/ardour-and-pipewire-jack-quantum-sample-rate-settings/113166 | | Bitwig Studio | Community comparison: moving from PipeWire back to real JACK let Pianoteq divide its buffer by 4, with a smaller Bitwig improvement | https://bbs.archlinux.org/viewtopic.php?id=304116&p=2 | -| SuperCollider scsynth | Native PipeWire backend sustains about 2500 voices versus 1500 through the JACK shim on one hybrid CPU — an artifact of core placement, not the shim | https://scsynth.org/t/experimental-native-pipewire-audio-backend-for-scsynth-linux-feedback-wanted/13292 | +| SuperCollider scsynth | Native PipeWire backend sustains about 2500 voices versus 1500 through the JACK shim on one hybrid CPU (an artifact of core placement, not the shim) | https://scsynth.org/t/experimental-native-pipewire-audio-backend-for-scsynth-linux-feedback-wanted/13292 | -These are one-machine anecdotes and forum posts, not controlled benchmarks; treat the exact numbers as indicative. The consistent picture: native engines on tuned systems run stable at 128-frame buffers and below, and REAPER and Bitwig set buffer size inside the application, which then drives the graph — the same shape PipeASIO's quantum matching gives Live on PipeWire 1.6+. +These are one-machine anecdotes and forum posts, not controlled benchmarks; treat the exact numbers as indicative. The consistent picture: native engines on tuned systems run stable at 128-frame buffers and below, and REAPER and Bitwig set buffer size inside the application, which then drives the graph: the same shape PipeASIO's quantum matching gives Live on PipeWire 1.6+. ## Gaps between this repo and the ecosystem diff --git a/notes/performance-moonshot/docs/MOONSHOT-OPPORTUNITIES.md b/notes/performance-moonshot/docs/MOONSHOT-OPPORTUNITIES.md index 13e1cff3..bd73cedd 100644 --- a/notes/performance-moonshot/docs/MOONSHOT-OPPORTUNITIES.md +++ b/notes/performance-moonshot/docs/MOONSHOT-OPPORTUNITIES.md @@ -88,7 +88,7 @@ hardware and time. | S14 | **Make wineserver persistent across launches (`wineserver -p`).** Cuts cold-start time and the per-launch `wineboot`. No steady-state audio effect. | Low-medium (launch feel) | Low | BUILD | | S15 | **Evaluate a PREEMPT_RT or lowlatency kernel as a supported configuration.** PREEMPT_RT is mainline since 6.12. It buys worst-case scheduling jitter, not average speed, and has measurable scheduling overheads. Recommend only with measured xrun deltas, not blanket advice. | Medium | Medium | AUDIO, BUILD | | S16 | **Evaluate proton-cachyos' `winepipewire.drv`** as an mmdevapi-level PipeWire reference for the non-ASIO paths (WebView2 pane audio, plugin preview sound). | Low | Medium | FORKS | -| S17 | **Audit Wine font APIs for Windows-lax behaviour Max relies on.** The vendored-font fix removed one M4L hang trigger, not the flaw; Wine's honesty about font failure turns Windows-latent Max defects into Live hangs. | Medium (prevents the next M4L hang class) | Medium | LIVE | +| S17 | **Audit Wine font APIs for Windows-lax behaviour Max relies on.** The vendored-font fix removed one M4L hang trigger, not the flaw; Wine reports font failures that Windows hides, which turns Windows-latent Max defects into Live hangs. | Medium (prevents the next M4L hang class) | Medium | LIVE | | S18 | **Revisit the WebView2 SwiftShader flags once dcomp compositing improves.** CPU rendering of every visible Learn pane is a standing UI-thread cost, accepted for correctness. Low effort to re-test; the fix depends on upstream dcomp work. | Medium (UI) | Low to re-test | BUILD | | S19 | **Run the missing WineASIO-versus-PipeASIO xrun comparison** under identical load. Confirms a decision already made; closes the last evidence gap from the 2026-07-17 evaluation. | Low | Medium | AUDIO, LIVE | | S20 | **Identify and implement the `wmvcore` export Live 11 calls.** Live 11 is experimental, so this is low priority. | Low | Medium | LIVE | diff --git a/notes/performance-moonshot/docs/OTHER-FORKS-SURVEY.md b/notes/performance-moonshot/docs/OTHER-FORKS-SURVEY.md index b7d880ef..1b453048 100644 --- a/notes/performance-moonshot/docs/OTHER-FORKS-SURVEY.md +++ b/notes/performance-moonshot/docs/OTHER-FORKS-SURVEY.md @@ -18,14 +18,14 @@ Already covered, so forks offering these add nothing: | Fork | Beyond upstream Wine | Targets | DAW relevance | Port difficulty | |---|---|---|---|---| | GE-Proton | Game fixes, media foundation, NVAPI, ntsync toggle | Games | Low | High | -| wine-tkg / proton-tkg | Build system with perf patch toggles | Games | Medium (mechanism, some patches) | Low–medium | -| wine-staging | ~100 experimental patch sets; a few perf-named | General + games | Medium | Low–medium | +| wine-tkg / proton-tkg | Build system with perf patch toggles | Games | Medium (mechanism, some patches) | Low-medium | +| wine-staging | ~100 experimental patch sets; a few perf-named | General + games | Medium | Low-medium | | Kron4ek Wine-Builds | Binaries; `-O3 -msse3` flags | General | Medium (flags only) | Trivial | | CachyOS proton-cachyos | Bleeding-edge Proton + winepipewire.drv + v3/LTO packaging | Games | Medium (audio driver) | Medium | | Valve Proton experimental | Per-game fixes, CPU topology, thread priorities | Games | Low (watch only) | High | | Proton-EM | winewayland.drv improvements | Games | Low (watch only) | High | | wine-wayland (upstream) | Native Wayland driver | General | Low in 2026 | Very high | -| Wine-NSPA | RT/audio fork: PI, IPC, client-side NT, io_uring | Pro audio | Highest | Medium–high | +| Wine-NSPA | RT/audio fork: PI, IPC, client-side NT, io_uring | Pro audio | Highest | Medium-high | | wine-rt / wineasio / yabridge | Historical RT patches; plugin bridge | Pro audio | Superseded / reference | n/a | | hangover / box64 | x86 on ARM64 emulation | ARM64 | None (x86-64 only) | n/a | @@ -60,7 +60,7 @@ wine-staging is Wine's experimental patch queue, a staging area for work not yet | `shell32-IconCache`, `dxgi_getFrameStatistics` | Icon cache; DXGI frame statistics API | Low | | `dsound-EAX` | Positional audio for games | None | -The APC set matters here. APC stands for asynchronous procedure call, a callback Windows queues onto a thread for delivery at its next alertable wait. Live's APC-coalescing thread burns 30–40% of a core at idle (`notes/ABLETON-WINE-APC-COALESCING.md:3`), and this repo has a written, unimplemented proposal to deliver same-process user APCs through the ntsync alert event instead of wineserver (`notes/ABLETON-WINE-APC-COALESCING.md:32-41`). Read `ntdll-APC_Performance` before writing that patch. Adoption difficulty for any staging set: low to medium; staging patches are formatted against the matching upstream Wine and this base is Wine 11.13 plus a fork's dcomp work, so context drift is the main risk. +The APC set matters here. APC stands for asynchronous procedure call, a callback Windows queues onto a thread for delivery at its next alertable wait. Live's APC-coalescing thread burns 30-40% of a core at idle (`notes/ABLETON-WINE-APC-COALESCING.md:3`), and this repo has a written, unimplemented proposal to deliver same-process user APCs through the ntsync alert event instead of wineserver (`notes/ABLETON-WINE-APC-COALESCING.md:32-41`). Read `ntdll-APC_Performance` before writing that patch. Adoption difficulty for any staging set: low to medium; staging patches are formatted against the matching upstream Wine and this base is Wine 11.13 plus a fork's dcomp work, so context drift is the main risk. ## Kron4ek Wine-Builds: compiler flags, not patches @@ -70,8 +70,8 @@ Kron4ek publishes vanilla, staging, staging-tkg, and proton-flavored Wine binari proton-cachyos tracks Valve's Proton experimental bleeding-edge, applies wine-staging, and imports winewayland.drv improvements from Proton-EM (https://github.com/CachyOS/proton-cachyos/releases, May 2026 entry). Two items stand out for an audio workload: -- `winepipewire.drv`, a native PipeWire backend for mmdevapi (the standard Windows audio API above ASIO), enabled by default, with a documented note that full `+pipewire` tracing perturbs audio timing (https://github.com/CachyOS/proton-cachyos). This repo serves Live through PipeASIO, but an mmdevapi-level PipeWire path covers everything that is not ASIO. -- Distro-level optimization: CachyOS rebuilds packages for x86-64-v3/v4 with LTO (https://wiki.cachyos.org/features/optimized_repos/). wine-cachyos exists as a separate build with ntsync support (https://discuss.cachyos.org/t/ntsync-in-latest-proton-cachyos-wine-cachyos/5254); its exact patch list is Unverified — the repository README is the stock Wine README (https://github.com/CachyOS/wine-cachyos). +- `winepipewire.drv`, a native PipeWire backend for mmdevapi (the standard Windows audio API above ASIO), enabled by default, with a documented warning that full `+pipewire` tracing perturbs audio timing (https://github.com/CachyOS/proton-cachyos). This repo serves Live through PipeASIO, but an mmdevapi-level PipeWire path covers everything that is not ASIO. +- Distro-level optimization: CachyOS rebuilds packages for x86-64-v3/v4 with LTO (https://wiki.cachyos.org/features/optimized_repos/). wine-cachyos exists as a separate build with ntsync support (https://discuss.cachyos.org/t/ntsync-in-latest-proton-cachyos-wine-cachyos/5254); its exact patch list is Unverified: the repository README is the stock Wine README (https://github.com/CachyOS/wine-cachyos). Relevance: medium. Adoption difficulty: medium for winepipewire.drv (one driver, new code rather than a conflict with the existing series), trivial for the compiler-flag idea. @@ -79,7 +79,7 @@ Relevance: medium. Adoption difficulty: medium for winepipewire.drv (one driver, Proton 11.0-1 rebased on Wine 11.0 and ships updated DXVK, vkd3d-proton, and Wine Mono; Proton experimental tracks it plus per-game fixes, current as of 2026-07-28 (https://github.com/ValveSoftware/Proton/wiki/Changelog). ntsync is the headline sync change: SteamOS 3.7.20 loads the ntsync module by default (https://www.phoronix.com/news/Steam-OS-Beta-NTSYNC, https://www.gamingonlinux.com/2026/01/steamos-3-7-20-adds-the-ntsync-driver-to-help-improve-some-game-performance/), and Proton 11 brings it to the Steam ecosystem (https://www.tweaktown.com/news/111106/valves-proton-11-beta-unlocks-more-playable-games-and-boosts-performance-for-steam-deck-and-linux-fans/index.html). -Three Proton changelog entries touch thread and CPU behavior rather than games: "Fixed Proton not setting priorities correctly for new threads" (9.0-4), "Fixed CPU topology override issues on machines with more than 32 logical cores" (10.0-1), and per-game core-count limits for old titles (9.0-1) (all: https://github.com/ValveSoftware/Proton/wiki/Changelog). Relevance: low for adoption — the fork's delta from mainline is huge and Steam-runtime-bound — but it is the fastest-moving public consumer of Wine 11 sync work. Treat it as an early-warning feed. +Three Proton changelog entries touch thread and CPU behavior rather than games: "Fixed Proton not setting priorities correctly for new threads" (9.0-4), "Fixed CPU topology override issues on machines with more than 32 logical cores" (10.0-1), and per-game core-count limits for old titles (9.0-1) (all: https://github.com/ValveSoftware/Proton/wiki/Changelog). Relevance: low for adoption (the fork's delta from mainline is huge and Steam-runtime-bound), but it is the fastest-moving public consumer of Wine 11 sync work. Treat it as an early-warning feed. ## Proton-EM: where Wayland fixes land first @@ -89,13 +89,13 @@ Proton-EM is Etaash Mathamsetty's Proton fork carrying winewayland.drv improveme The upstream Wayland driver is improving but still acquiring windowing basics in mid-2026: Wine 11.0 shipped "better Wine Wayland driver support" (https://www.phoronix.com/news/Wine-11.0-Released), Wine 11.11 added layered windows and min/max size hints (https://www.phoronix.com/news/Wine-11.11-Released), Wine 11.12 added fractional scaling (https://www.phoronix.com/linux/WINE news archive, 29 June 2026 entry), and alpha-modifier support landed the same month (https://www.phoronix.com/news/Wine-Wayland-Alpha-Modifier). Downstream consumers still treat it as experimental: proton-cachyos documents white-window failures for CEF/Electron apps and notes Proton 11 removed Proton 10's automated Wayland hacks (https://github.com/CachyOS/proton-cachyos); GE-Proton notes Steam overlay and Steam Input do not work with the driver (https://github.com/GloriousEggroll/proton-ge-custom). -This project's series is deeply winex11-shaped — winex11 changes run through patches 0002–0017 and recur at 0039, 0042, 0053, and 0062 (`patches/BASE.txt:14` and the per-patch provenance list) — and Live's validated configuration is XWayland (see the resize and menu notes under `notes/`). Migration would port or discard most of that work for an unclear gain. Verdict: keep winex11; re-check the driver after it stops landing per-release windowing basics. +This project's series is deeply winex11-shaped: winex11 changes run through patches 0002-0017 and recur at 0039, 0042, 0053, and 0062 (`patches/BASE.txt:14` and the per-patch provenance list), and Live's validated configuration is XWayland (see the resize and menu notes under `notes/`). Migration would port or discard most of that work for an unclear gain. Verdict: keep winex11; re-check the driver after it stops landing per-release windowing basics. ## Wine-NSPA: the pro-audio fork to mine -Wine-NSPA (nine7nine) is a PREEMPT_RT-focused fork of Wine 11.8 for pro audio — PREEMPT_RT is the kernel patch set that makes Linux fully preemptible for real-time workloads (https://github.com/nine7nine/Wine-NSPA). Its documented work, per the README's architecture index and status: +Wine-NSPA (nine7nine) is a PREEMPT_RT-focused fork of Wine 11.8 for pro audio: PREEMPT_RT is the kernel patch set that makes Linux fully preemptible for real-time workloads (https://github.com/nine7nine/Wine-NSPA). Its documented work, per the README's architecture index and status: -- Priority inheritance (PI) for `CRITICAL_SECTION` and Win32 condition variables, via a bundled librtpi re-implementation. PI temporarily raises a lock holder's priority to prevent priority inversion — directly relevant to this repo's noted risk that Live's real-time threads outrank the `SCHED_OTHER` wineserver they synchronously call (`notes/ABLETON-WINE-RT-SCHEDULING.md:15-20`). +- Priority inheritance (PI) for `CRITICAL_SECTION` and Win32 condition variables, via a bundled librtpi re-implementation. PI temporarily raises a lock holder's priority to prevent priority inversion: directly relevant to this repo's noted risk that Live's real-time threads outrank the `SCHED_OTHER` wineserver they synchronously call (`notes/ABLETON-WINE-RT-SCHEDULING.md:15-20`). - A kernel-mediated wineserver IPC layer ("gamma channel dispatcher") with aggregate-wait and burst drain, plus a kernel-side ntsync PI overlay in the companion Linux-NSPA kernel (https://github.com/nine7nine/Linux-NSPA-pkgbuild). - Client-side NT surfaces: local events, local timers, local files, local sections, and thread/process shared-state readers that turn some waits into "zero-time" waits without a wineserver round trip. - `io_uring` (Linux's shared-ring async I/O interface) for file and socket I/O. @@ -104,13 +104,13 @@ Wine-NSPA (nine7nine) is a PREEMPT_RT-focused fork of Wine 11.8 for pro audio - Audio: winejack and nspaASIO drivers; embedding protocols that let winelib hosts (winelib is Wine's library for building Unix applications against the Win32 API) embed Wine HWND plugin editors over X11 or Wayland; a Yabridge-NSPA bridge fork. - Stated validation: native ntsync suite 3 PASS / 0 FAIL, PE matrix 32 PASS / 0 FAIL / 0 TIMEOUT (`v9-validation-default`). -This is the only surveyed fork built for the same workload class as this project, and the relationship already exists: patches 0002–0003 here are winex11 changes from nine7nine/wine-nspa-src commits (`patches/BASE.txt:34-36`). Community reports also credit the Wine-NSPA ecosystem's Ableton Options.txt tuning with large CPU reductions under Wine (https://github.com/nine7nine/Wine-NSPA/issues/4). Two cautions. First, the 11.x repository publishes "design, architecture, and validation documentation"; whether full 11.x sources or patch files are public is Unverified — verify before planning ports. Second, several items (kernel IPC overlay, PI ntsync) assume the custom Linux-NSPA kernel, which this project does not ship; the client-side items (message ring, empty-poll caching, local events/timers, `mlockall`) are the portable subset. Adoption difficulty: medium-high per item, high for the kernel-dependent items. +This is the only surveyed fork built for the same workload class as this project, and the relationship already exists: patches 0002-0003 here are winex11 changes from nine7nine/wine-nspa-src commits (`patches/BASE.txt:34-36`). Community reports also credit the Wine-NSPA ecosystem's Ableton Options.txt tuning with large CPU reductions under Wine (https://github.com/nine7nine/Wine-NSPA/issues/4). Two cautions. First, the 11.x repository publishes "design, architecture, and validation documentation"; whether full 11.x sources or patch files are public is Unverified. Verify before planning ports. Second, several items (kernel IPC overlay, PI ntsync) assume the custom Linux-NSPA kernel, which this project does not ship; the client-side items (message ring, empty-poll caching, local events/timers, `mlockall`) are the portable subset. Adoption difficulty: medium-high per item, high for the kernel-dependent items. ## wine-rt, wineasio, yabridge: the real-time lineage - wine-rt: a 2013-era patch that gave Wine threads `SCHED_FIFO` via the `WINE_RT` environment variable (https://github.com/PlayOnLinux/wine-patches/blob/master/custom/RealTime/rt.patch); KXStudio shipped an rt-patched Wine for audio work (https://forum.winehq.org/viewtopic.php?t=32742). Superseded by this repo's launcher-level `chrt` policy (`notes/ABLETON-WINE-RT-SCHEDULING.md:1-11`). Nothing to adopt. - wineasio: the classic ASIO-to-JACK driver. This project ships PipeASIO instead (see `notes/ABLETON-WINE-PIPEASIO.md`). Nothing to adopt. -- yabridge: runs Windows VST2/VST3/CLAP plugins in Wine for native Linux hosts, bridging over shared memory and UNIX sockets with under 1 ms added latency in one 2026 account (https://bonnef.in/posts/linux-music-production/, project at https://github.com/robbert-vdh/yabridge). Live loads plugins in-process here, so the bridge is not needed; its Yabridge-NSPA fork is worth tracking as a bellwether for Wine-NSPA's RT rules. +- yabridge: runs Windows VST2/VST3/CLAP plugins in Wine for native Linux hosts, bridging over shared memory and UNIX sockets with under 1 ms added latency in one 2026 account (https://bonnef.in/posts/linux-music-production/, project at https://github.com/robbert-vdh/yabridge). Live loads plugins in-process here, so the bridge is not needed; track its Yabridge-NSPA fork as an early indicator of Wine-NSPA's RT rules. ## hangover and box64: out of scope @@ -118,8 +118,8 @@ hangover pairs Wine with the FEX or Box64 emulators to run x86 Windows applicati ## Key opportunities -1. **Mine Wine-NSPA's portable client-side work** — message ring, empty-poll caching, local events/timers, shared-state waits, `mlockall` — as individual patches, starting with whichever maps to the largest measured wineserver load. Impact: high. Effort: high. Evidence: https://github.com/nine7nine/Wine-NSPA (documented highlights); existing port precedent at `patches/BASE.txt:34-36`. -2. **Read wine-staging's `ntdll-APC_Performance` before implementing the proposed same-process APC bypass.** Impact: high (targets a measured 30–40% idle core and suspected playback xruns). Effort: medium. Evidence: `notes/ABLETON-WINE-APC-COALESCING.md:3` and `:32-41`; patch set listed at https://github.com/wine-staging/wine-staging/tree/master/patches. +1. **Mine Wine-NSPA's portable client-side work** (message ring, empty-poll caching, local events/timers, shared-state waits, `mlockall`) as individual patches, starting with whichever maps to the largest measured wineserver load. Impact: high. Effort: high. Evidence: https://github.com/nine7nine/Wine-NSPA (documented highlights); existing port precedent at `patches/BASE.txt:34-36`. +2. **Read wine-staging's `ntdll-APC_Performance` before implementing the proposed same-process APC bypass.** Impact: high (targets a measured 30-40% idle core and suspected playback xruns). Effort: medium. Evidence: `notes/ABLETON-WINE-APC-COALESCING.md:3` and `:32-41`; patch set listed at https://github.com/wine-staging/wine-staging/tree/master/patches. 3. **Benchmark an `-O3 -march=x86-64-v3` build against the current default-flags build.** Impact: medium. Effort: low. Evidence: no `CFLAGS`/`-march` in `scripts/container-build.sh:53-56`; Kron4ek ships `-O3 -msse3` (https://github.com/Kron4ek/Wine-Builds); CachyOS ships v3/v4+LTO repos (https://wiki.cachyos.org/features/optimized_repos/). 4. **Audit Windows thread-priority mapping against the launcher's `SCHED_RR` inheritance, and check whether Proton's new-thread priority fix has an upstream equivalent.** Impact: medium. Effort: medium. Evidence: "Fixed Proton not setting priorities correctly for new threads" (https://github.com/ValveSoftware/Proton/wiki/Changelog, 9.0-4); priority-inversion risk noted at `notes/ABLETON-WINE-RT-SCHEDULING.md:15-20`. 5. **Evaluate proton-cachyos' `winepipewire.drv` as an mmdevapi-level PipeWire reference for non-ASIO audio paths.** Impact: low. Effort: medium. Evidence: enabled by default with latency-tuning notes at https://github.com/CachyOS/proton-cachyos. diff --git a/notes/performance-moonshot/docs/OWN-FORK-BUILD-AND-RUNTIME.md b/notes/performance-moonshot/docs/OWN-FORK-BUILD-AND-RUNTIME.md index e6eac06c..02866360 100644 --- a/notes/performance-moonshot/docs/OWN-FORK-BUILD-AND-RUNTIME.md +++ b/notes/performance-moonshot/docs/OWN-FORK-BUILD-AND-RUNTIME.md @@ -52,7 +52,7 @@ The build ships Wine's configure defaults. Nothing raises them. | `WINE_CPU_TOPOLOGY` | capped at 8 CPUs | groundwork only: "Inert on this runtime until the patched ntdll/wineserver consumer lands" | `scripts/ableton-live:75-108` | | `WINESERVER` | this build's wineserver | binds the prefix to the patched server | `scripts/ableton-live:27` | -Notable absences and behaviors: +Absences and behaviors: - `WINEESYNC`/`WINEFSYNC` are explicitly unset by the portal wrappers and setup (`bin/ableton-live-portal:24`, `bin/ableton-wine-portal:15`, `scripts/setup-prefix.sh:56`). ntsync is the synchronization path; nothing to add here. - No `/dev/ntsync` presence check at launch. On a kernel without it, every NT synchronization wait becomes a wineserver round trip, and the user gets no warning. The build-time comment prices this at "~1.3 cores with Live running" (`scripts/container-build.sh:104-107`); the regression note measured ~45% of one core and ~9,000 context switches per second at idle with the ASIO device open (`notes/ABLETON-WINE-NTSYNC-REGRESSION.md:11-14`). @@ -74,7 +74,7 @@ Notable absences and behaviors: | lowlatency / PREEMPT_RT kernel | advised only, for sub-256-frame buffers | `scripts/setup-realtime.sh:146-151` | | wineserver `chrt -f 95` boost | deliberately omitted (root per launch; priority inversion risk) | `scripts/setup-realtime.sh:23-25` | -At launch, the launcher probes `chrt -r 10 true` and runs the whole Wine process tree under `SCHED_RR` priority 10 when the probe succeeds (`scripts/ableton-live:780-783`). SCHED_RR is a realtime round-robin policy: GUI threads inherit it along with audio threads. PipeASIO separately requests `SCHED_FIFO` priority 15 for its data-loop thread (`notes/ABLETON-WINE-RT-SCHEDULING.md:3-7`). The same note lists the open risks — Linux throttles realtime tasks to 950 ms per second by default, all inherited threads share one RR priority, and Live's realtime threads outrank the `SCHED_OTHER` wineserver they make synchronous calls to (`notes/ABLETON-WINE-RT-SCHEDULING.md:31-41`) — and prescribes a pending 4-CPU A/B comparison before changing the default (`notes/ABLETON-WINE-RT-SCHEDULING.md:43-82`). That comparison is still unrun; no `bench/` directory or committed results exist in the repo. +At launch, the launcher probes `chrt -r 10 true` and runs the whole Wine process tree under `SCHED_RR` priority 10 when the probe succeeds (`scripts/ableton-live:780-783`). SCHED_RR is a realtime round-robin policy: GUI threads inherit it along with audio threads. PipeASIO separately requests `SCHED_FIFO` priority 15 for its data-loop thread (`notes/ABLETON-WINE-RT-SCHEDULING.md:3-7`). The same note lists the open risks (Linux throttles realtime tasks to 950 ms per second by default, all inherited threads share one RR priority, and Live's realtime threads outrank the `SCHED_OTHER` wineserver they make synchronous calls to (`notes/ABLETON-WINE-RT-SCHEDULING.md:31-41`)) and prescribes a pending 4-CPU A/B comparison before changing the default (`notes/ABLETON-WINE-RT-SCHEDULING.md:43-82`). That comparison is still unrun; no `bench/` directory or committed results exist in the repo. rtkit (a D-Bus service that grants realtime scheduling to unprivileged clients) is not referenced anywhere in the repo. PipeWire uses it for its own data loops on hosts where it is installed; this project neither requires nor verifies it. @@ -82,10 +82,10 @@ rtkit (a D-Bus service that grants realtime scheduling to unprivileged clients) - PipeASIO is the only shipped ASIO driver; it is a native PipeWire client with no JACK layer (`notes/ABLETON-WINE-PIPEASIO.md:3-5`). - Prefix setup seeds `~/.config/pipeasio/config.ini`: 2 inputs, 2 outputs, fixed 256-frame buffer, auto-connect (`scripts/setup-prefix.sh:591-603`). `PIPEASIO_*` variables override per launch (`scripts/ableton-live:760-762`). -- PipeWire 1.6 or newer can match the graph quantum (the processing cycle size in frames) to the ASIO buffer; a 256-frame configuration produced `force-quantum` 256 in validation (`notes/ABLETON-WINE-PIPEASIO.md:20,72`). The runtime accepts host PipeWire 0.3.56 or newer (`Containerfile:100-105`), so hosts on 0.3.x–1.5 get no quantum matching and nothing warns them. The README recommends 1.6+ (`README.md:40`); TROUBLESHOOTING repeats the version check (`TROUBLESHOOTING.md:106`). +- PipeWire 1.6 or newer can match the graph quantum (the processing cycle size in frames) to the ASIO buffer; a 256-frame configuration produced `force-quantum` 256 in validation (`notes/ABLETON-WINE-PIPEASIO.md:20,72`). The runtime accepts host PipeWire 0.3.56 or newer (`Containerfile:100-105`), so hosts on 0.3.x-1.5 get no quantum matching and nothing warns them. The README recommends 1.6+ (`README.md:40`); TROUBLESHOOTING repeats the version check (`TROUBLESHOOTING.md:106`). - No PipeWire host configuration (clock rate, quantum, wireplumber device rules) is seeded or verified by any script. The only quantum evidence path is manual `pw-metadata -n settings` / `pw-top`, referenced by the tester kit (`beta/tester-kit/lib/collect-linux.sh:148-149`) and the audio check's failure hint (`scripts/check-live-audio.sh:57-60`). - Validation recorded ~8% Live DSP load at 48 kHz / 256 frames, with the PipeWire error counter moving 24→26 on a loaded machine; the note itself flags this as not a controlled latency comparison (`notes/ABLETON-WINE-PIPEASIO.md:83-85`). -- The one measured audio-adjacent CPU sink outside the driver is Live's APC-coalescing thread: 30–40% of one core at idle. ntsync does not accelerate the alertable waits behind it. The attempted `-DontCombineAPCs` workaround caused playback starvation and was reverted; a Wine-side fix is proposed but unimplemented (`notes/ABLETON-WINE-APC-COALESCING.md:3-16`). +- The one measured audio-adjacent CPU sink outside the driver is Live's APC-coalescing thread: 30-40% of one core at idle. ntsync does not accelerate the alertable waits behind it. The attempted `-DontCombineAPCs` workaround caused playback starvation and was reverted; a Wine-side fix is proposed but unimplemented (`notes/ABLETON-WINE-APC-COALESCING.md:3-16`). ## Kernel requirements @@ -96,11 +96,11 @@ rtkit (a D-Bus service that grants realtime scheduling to unprivileged clients) | lowlatency / PREEMPT_RT kernel | sub-256-frame buffers | advised by setup-realtime.sh only | `scripts/setup-realtime.sh:146-151` | | glibc ≥ 2.35 | runtime floor of the tarball | stated in BUILD-INFO | `scripts/container-build.sh:230` | -ntsync's measured value: 4–50x more synchronization throughput after it was restored, and wineserver idle load dropped from ~45% of a core (`notes/ABLETON-WINE-NTSYNC-REGRESSION.md:11-14`). +ntsync's measured value: 4-50x more synchronization throughput after it was restored, and wineserver idle load dropped from ~45% of a core (`notes/ABLETON-WINE-NTSYNC-REGRESSION.md:11-14`). ## Benchmarking and probe coverage -The harness is `scripts/bench-run.sh`. Its protocol: before/after row pairs under fixed reference conditions — a committed reference set, 48 kHz / 256 frames, fixed window geometry, one machine per comparison (`scripts/bench-run.sh:7-11`). Four metrics: +The harness is `scripts/bench-run.sh`. Its protocol: before/after row pairs under fixed reference conditions: a committed reference set, 48 kHz / 256 frames, fixed window geometry, one machine per comparison (`scripts/bench-run.sh:7-11`). Four metrics: | Metric | How captured | Evidence | |---|---|---| @@ -112,7 +112,7 @@ The harness is `scripts/bench-run.sh`. Its protocol: before/after row pairs unde Coverage gaps: - No automated xrun capture. The headline metric is typed in by a human reading `pw-top`. -- No round-trip latency measurement (loopback), no DSP-load automation, no startup-time metric, no UI-responsiveness metric (frame pacing, input latency), no idle-CPU metric for the APC-coalescing thread — the APC note says to record those "separately unless that script is extended" (`notes/ABLETON-WINE-APC-COALESCING.md:59-63`). +- No round-trip latency measurement (loopback), no DSP-load automation, no startup-time metric, no UI-responsiveness metric (frame pacing, input latency), no idle-CPU metric for the APC-coalescing thread. The APC note says to record those "separately unless that script is extended" (`notes/ABLETON-WINE-APC-COALESCING.md:59-63`). - The "committed reference set" the protocol requires is not in the repo: no `bench/` directory exists. Unverified: testers may hold it privately; either way it is not committed. - Gates that do exist: `scripts/check-ntsync.sh` (semantics probe + `/dev/ntsync` open check, with wineserver context-switch delta at `scripts/check-ntsync.sh:52-62`), `scripts/check-live-audio.sh` (Live log scan for a clean ASIO open), `scripts/check-m4l-fonts.sh` (font-fallback hang regression), `scripts/build-audit.sh` (per-patch artifact fingerprints). These are correctness gates, not performance gates. - `tools/` probes are diagnostic, not benchmarks: `mousespy.c` (global mouse-hook routing trace), `linkprobe.c` (Link multicast verdict), `midihot.c` (MIDI hotplug listener), `metricprobe2.c` (DPI metric pinning), `stresstest.c` (session-allocator hammer), `xsamp.c`/`xrec.c` (X-side pixel and protocol spies). None measures latency or throughput under load. @@ -127,7 +127,7 @@ Ranked by expected impact on xrun-free low-latency audio and UI responsiveness. 4. **Raise compiler optimization: `-O3` plus ThinLTO on the clang PE side, `-O3 -fno-plt` on hot Unix halves.** clang 21 + lld is already the PE toolchain; ThinLTO needs no new dependency. Unverified: PE build survival under LTO, and the size of the win; gate on the existing relocation gate plus a bench pair. Impact: medium-high. Effort: medium. Evidence: `scripts/container-build.sh:53-56` (no flag overrides), `Containerfile:13-17`. 5. **Seed and verify PipeWire host configuration.** Planned: at setup or launch, check the PipeWire version (quantum matching needs 1.6+), verify or set the device's clock rate and quantum via metadata/wireplumber rules, and warn when the graph quantum disagrees with the ASIO buffer. Impact: medium-high (a mismatched quantum is a direct xrun source; today nothing checks it). Effort: low-medium. Evidence: `notes/ABLETON-WINE-PIPEASIO.md:20,72`, `TROUBLESHOOTING.md:106`, `scripts/check-live-audio.sh:57-60`. 6. **Ship a `-march=x86-64-v2` build (SSE4.2/PopCNT, 2009-era floor), optionally with a v3 variant.** The tarball currently targets the generic baseline. v2 is near-universal on machines that run Live 12; v3 (AVX2) could be a separate opt-in artifact. Impact: medium. Effort: low (one flag each side, once). Evidence: `scripts/container-build.sh:53-56`; relocatable-tarball policy at `build.sh:15-17`. -7. **Complete the `WINE_CPU_TOPOLOGY` consumer so the 8-CPU cap actually applies.** The launcher computes and exports the cap but marks it inert; on high-core machines Live sizes thread pools from the full CPU count. Impact: medium on >8-core machines. Effort: medium (the consumer is a wineserver/ntdll change — patch territory; the launcher half is done). Evidence: `scripts/ableton-live:75-108`. +7. **Complete the `WINE_CPU_TOPOLOGY` consumer so the 8-CPU cap actually applies.** The launcher computes and exports the cap but marks it inert; on high-core machines Live sizes thread pools from the full CPU count. Impact: medium on >8-core machines. Effort: medium (the consumer is a wineserver/ntdll change, patch territory; the launcher half is done). Evidence: `scripts/ableton-live:75-108`. 8. **Automate IRQ affinity instead of advising it.** Planned: package or vendor an rtirq equivalent, and fail the realtime check when `threadirqs` is missing rather than printing a note once at setup. Impact: medium. Effort: medium (host policy surface). Evidence: `scripts/setup-realtime.sh:124-144`. 9. **Add a kernel check to the realtime report: lowlatency/PREEMPT_RT and `threadirqs` presence, surfaced by the launcher or tester kit.** Sub-256-frame buffers are the project's own stated case for these kernels; today the advice prints once during setup. Impact: medium. Effort: low. Evidence: `scripts/setup-realtime.sh:146-151`, `beta/tester-kit/lib/collect-linux.sh:148-154`. 10. **Upgrade the Unix-side compiler past gcc 11.2 inside the same jammy image.** Keeps the glibc 2.35 floor while picking up several years of code-generation improvements. Unverified: exact gain; measure with a bench pair. Impact: low-medium. Effort: low-medium (one apt pin in `Containerfile`). Evidence: `Containerfile:37-39`, [packages.ubuntu.com/jammy/gcc](https://packages.ubuntu.com/jammy/gcc), floor at `scripts/container-build.sh:230`. diff --git a/notes/performance-moonshot/docs/OWN-FORK-PATCH-MAP.md b/notes/performance-moonshot/docs/OWN-FORK-PATCH-MAP.md index 2ea5dcdf..dd02bf0e 100644 --- a/notes/performance-moonshot/docs/OWN-FORK-PATCH-MAP.md +++ b/notes/performance-moonshot/docs/OWN-FORK-PATCH-MAP.md @@ -22,7 +22,7 @@ One table per category. Columns: ## Terms used in this document -- **Non-client (NC) area**: the window zone outside the app's drawing area — title bar, borders, menu band. Live draws its own, which is why so many patches touch it. +- **Non-client (NC) area**: the window zone outside the app's drawing area: title bar, borders, menu band. Live draws its own, which is why so many patches touch it. - **MWM / Motif hints**: X11 properties that tell the window manager which decorations and buttons a window wants. - **Frame extents**: the `_NET_FRAME_EXTENTS` X11 property reporting the window manager's frame thickness. - **Override-redirect**: an X11 window the window manager ignores; Wine uses it for popup menus. @@ -41,8 +41,8 @@ The largest category. Most entries fight window-growth loops, double frames, or | Patch | Purpose | Relevance | Origin | Note | |---|---|---|---|---| -| 0002 NSPA visible-rect + decoration gates | Moves the window==client check after the style-mask lookup so custom-NC apps still get decoration masking; stops a 4 px/frame growth loop (Wine bug 57955) | stability | Experimental — from nine7nine/wine-nspa-src (`patches/BASE.txt:34-36`) | `notes/ABLETON-WINE-REBASE-11.13.md:21` (bug 57955 open as of 2026-07-17; current status Unverified — bugs.winehq.org was not reachable for re-check) | -| 0003 NSPA 1c frame extents + reentrancy | Frame-extents handling, reentrancy suppression, ncsize guard; fixes the winex11 atom enum order locally | stability | Experimental — nine7nine plus local fix (`patches/BASE.txt:34-36`) | same as 0002 | +| 0002 NSPA visible-rect + decoration gates | Moves the window==client check after the style-mask lookup so custom-NC apps still get decoration masking; stops a 4 px/frame growth loop (Wine bug 57955) | stability | Experimental, from nine7nine/wine-nspa-src (`patches/BASE.txt:34-36`) | `notes/ABLETON-WINE-REBASE-11.13.md:21` (bug 57955 open as of 2026-07-17; current status Unverified: bugs.winehq.org was not reachable for re-check) | +| 0003 NSPA 1c frame extents + reentrancy | Frame-extents handling, reentrancy suppression, ncsize guard; fixes the winex11 atom enum order locally | stability | Experimental, nine7nine plus local fix (`patches/BASE.txt:34-36`) | same as 0002 | | 0004 GNOME per-thread reentrant state | Makes reentrant-WM_WINDOWPOSCHANGED state per-thread (menu wedge race); drops MWM decor for custom-NC windows | stability | Local (`patches/BASE.txt:37-38`); plausibly upstreamable, general reentrancy bug | BASE | | 0005 no frame allowance for custom-NC | Removes the white rim around Live's modal dialogs | correctness | Local; Live-specific visual fix | BASE | | 0006 disable frame-extents reconstruction | Stops comdlg32 dialogs landing off-screen at high DPI | stability | Local workaround; later reverted and re-instated by 0008/0009 | `notes/ABLETON-WINE-RESIZE-BUG.md` | @@ -67,8 +67,8 @@ One Wine patch and the two PipeASIO driver patches. No patch touches Wine's audi | Patch | Purpose | Relevance | Origin | Note | |---|---|---|---|---| | 0021 mmdevapi FriendlyName re-wrap | Stops wrapping a stored endpoint FriendlyName again on every reload; the multi-level names crashed or hung Live's device enumeration | stability | Local; upstreamable, general bug | `notes/ABLETON-WINE-AUDIO-CRASH-BUG.md` | -| pipeasio 0001 keep graph sample rate | Reports success at the PipeWire graph rate instead of ASE_NoClock; Live treated the refusal as fatal and crash-looped on fresh installs (`patches/pipeasio/0001-asio-keep-graph-sample-rate-instead-of-ASE_NoClock.patch:14-30`) | stability | Experimental — downstream patch to vendored PipeASIO 1.2.2 | `notes/ABLETON-WINE-PIPEASIO.md` | -| pipeasio 0002 timeGetTime systemTime | Reports `timeGetTime()` as the ASIO systemTime so Live stops dropping live-played MIDI as out of window (`patches/pipeasio/0002-asio-report-timeGetTime-in-ASIO-systemTime.patch:17`) | correctness | Experimental — downstream PipeASIO patch | `notes/ABLETON-WINE-PIPEASIO.md` | +| pipeasio 0001 keep graph sample rate | Reports success at the PipeWire graph rate instead of ASE_NoClock; Live treated the refusal as fatal and crash-looped on fresh installs (`patches/pipeasio/0001-asio-keep-graph-sample-rate-instead-of-ASE_NoClock.patch:14-30`) | stability | Experimental, downstream patch to vendored PipeASIO 1.2.2 | `notes/ABLETON-WINE-PIPEASIO.md` | +| pipeasio 0002 timeGetTime systemTime | Reports `timeGetTime()` as the ASIO systemTime so Live stops dropping live-played MIDI as out of window (`patches/pipeasio/0002-asio-report-timeGetTime-in-ASIO-systemTime.patch:17`) | correctness | Experimental, downstream PipeASIO patch | `notes/ABLETON-WINE-PIPEASIO.md` | ## MIDI and input @@ -93,7 +93,7 @@ The second-largest category. It holds the fork's DirectComposition reblit machin | Patch | Purpose | Relevance | Origin | Note | |---|---|---|---|---| -| 0001 sashaduke redraw patchset | Implements dcomp/dxgi frame statistics, refresh-rate-aware WaitForVBlank, refresh-rate fallback in swapchain descs, color-space semi-stubs, `WINED3D_DCOMP_FORCE_FULL_REDRAW`, and cs.c assert relaxations | performance + stability | Experimental — third-party patchset from sashaduke/ableton-live12-linux (`patches/BASE.txt:32-33`) | `notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md` | +| 0001 sashaduke redraw patchset | Implements dcomp/dxgi frame statistics, refresh-rate-aware WaitForVBlank, refresh-rate fallback in swapchain descs, color-space semi-stubs, `WINED3D_DCOMP_FORCE_FULL_REDRAW`, and cs.c assert relaxations | performance + stability | Experimental, third-party patchset from sashaduke/ableton-live12-linux (`patches/BASE.txt:32-33`) | `notes/ABLETON-WINE-GPU-RENDERER-WEBVIEW2-DIAGNOSIS.md` | | 0016 orphaned dcomp target subclass | Keeps the window's true original wndproc; an orphaned subclass swallowed all mouse input on JUCE D2D editors | stability | Local; flagged upstreamable (`notes/ABLETON-WINE-INPUT-BUG.md:93`) | `notes/ABLETON-WINE-INPUT-BUG.md` | | 0020 sRGB pixel formats on EGL | Advertises and honors sRGB-capable formats; baseview/nih-plug editors aborted Live without one | stability | Local; upstreamable | `notes/ABLETON-WINE-INPUT-BUG.md` | | 0022 reblit timer stops forcing Present | Timer ticks signal the frame-latency event and refresh from the comp buffer instead of forcing a Present | performance | Local; experimental, fork-specific reblit design | BASE (`patches/BASE.txt:37-38` range; message at `patches/0022-dxgi-stop-forcing-swapchain-presents-from-the-dcomp-.patch:1`) | @@ -117,8 +117,8 @@ The second-largest category. It holds the fork's DirectComposition reblit machin | 0023 present/resize rects in window's DPI context | Brackets the present-time client-rect queries with the window's own DPI awareness context | correctness | Local; upstreamable | `notes/ABLETON-WINE-PIANOTEQ-DPI-GHOST-BUG.md:32-35` | | 0024 present/resize DPI diagnostics at trace | Demotes the PRESENT-DBG/RESIZE-DBG probes from fixme to trace | correctness | Local; fork diagnostics, not for upstream | `notes/ABLETON-WINE-PIANOTEQ-DPI-GHOST-BUG.md:37-38` | | 0029 menu bar SM_CYMENU + 4 | Lays out the menu band 4 px taller so NCCALCSIZE matches Live's outer-rect model at 96 DPI | correctness | Local; Live-specific geometry matching | `notes/ABLETON-WINE-DPI-SCALE-100.md` (per `patches/BASE.txt:43-45`) | -| 0040 scale menu band with menu DPI | Makes the band max(4, muldiv(4, dpi, 96) − 1); the resize negotiation converges in one pass at 125–200% | stability + correctness | Local; Live-specific geometry matching | `notes/ABLETON-WINE-DPI-SCALE-100.md`, `notes/FINDINGS-RESIZE-GROWTH-2026-07-21.md` | -| 0042 alias sub-scale WM config rounding | Treats sub-scale grant/request differences as compositor rounding instead of feeding them to Win32; stops the 2 px/cycle growth and answers in-band requests locally, removing one request per pointer motion during drags | stability + performance | Adapted from ENCORE (`patches/BASE.txt:86-90`); experimental workaround — an upstream *report* is drafted, not a patch | `notes/FINDINGS-RESIZE-GROWTH-2026-07-21.md`, `notes/ABLETON-WINE-DPI-SCALE-100.md`, `notes/ABLETON-WINE-RESIZE-BUG.md`, `notes/UPSTREAM-ISSUE-DRAFT-RESIZE-PARITY.md`, `notes/ABLETON-WINE-ENCORE-REVIEW.md` | +| 0040 scale menu band with menu DPI | Makes the band max(4, muldiv(4, dpi, 96) − 1); the resize negotiation converges in one pass at 125-200% | stability + correctness | Local; Live-specific geometry matching | `notes/ABLETON-WINE-DPI-SCALE-100.md`, `notes/FINDINGS-RESIZE-GROWTH-2026-07-21.md` | +| 0042 alias sub-scale WM config rounding | Treats sub-scale grant/request differences as compositor rounding instead of feeding them to Win32; stops the 2 px/cycle growth and answers in-band requests locally, removing one request per pointer motion during drags | stability + performance | Adapted from ENCORE (`patches/BASE.txt:86-90`); experimental workaround (an upstream *report* is drafted, not a patch) | `notes/FINDINGS-RESIZE-GROWTH-2026-07-21.md`, `notes/ABLETON-WINE-DPI-SCALE-100.md`, `notes/ABLETON-WINE-RESIZE-BUG.md`, `notes/UPSTREAM-ISSUE-DRAFT-RESIZE-PARITY.md`, `notes/ABLETON-WINE-ENCORE-REVIEW.md` | ## Portals and desktop integration @@ -139,7 +139,7 @@ All five serve the native win32 menu chrome that the launcher themes to match Li | 0050 invalidate sys-color cache on WM_SYSCOLORCHANGE | Re-reads colors and frees cached brushes/pens when another process calls SetSysColors | correctness | Local; upstreamable | `notes/ABLETON-WINE-MENU-COLOR-THEMING.md:139,226`, `notes/FINDINGS-LIVE-THEME-PREVIEW-SIGNAL-2026-07-26.md` | | 0051 SetSysColors repaints non-client area | Adds RDW_FRAME to the forced repaint so menu bars and captions follow | correctness | Local; upstreamable | `notes/ABLETON-WINE-MENU-COLOR-THEMING.md:151,227` | | 0052 hide menu-bar mnemonic underlines | Hides the alt-key underlines real Windows only shows after Alt; also makes the dead DT_HIDEPREFIX flag actually work in user32 | correctness | Mixed: the DT_HIDEPREFIX gating fix is upstreamable; hiding the underlines is a style choice (`patches/BASE.txt:138-148`) | `notes/ABLETON-WINE-MENU-COLOR-THEMING.md:196,228` | -| 0054 linked-font fallback for menu glyphs | Falls back to SystemLink families when the Ableton Sans substitute lacks a glyph; measures with the same fallback font | correctness | Local; experimental — whole-string swap trade-off recorded in the commit message | `notes/ABLETON-WINE-MENU-FONT-FALLBACK.md` | +| 0054 linked-font fallback for menu glyphs | Falls back to SystemLink families when the Ableton Sans substitute lacks a glyph; measures with the same fallback font | correctness | Local; experimental (whole-string swap trade-off recorded in the commit message) | `notes/ABLETON-WINE-MENU-FONT-FALLBACK.md` | ## Live-specific workarounds @@ -147,9 +147,9 @@ Patches that exist only because of Live's behavior or hardware. | Patch | Purpose | Relevance | Origin | Note | |---|---|---|---|---| -| 0032 host USB bridge for Push 2 | Exports the 16-function Win64 libusb 1.0.23 ABI so Push2DisplayProcess.exe drives the Push 2 display through host libusb | stability | Experimental — helper-scoped bridge, i386 half disabled (`patches/BASE.txt:52-55`) | `notes/ABLETON-WINE-PUSH2-DISPLAY.md` | +| 0032 host USB bridge for Push 2 | Exports the 16-function Win64 libusb 1.0.23 ABI so Push2DisplayProcess.exe drives the Push 2 display through host libusb | stability | Experimental, helper-scoped bridge, i386 half disabled (`patches/BASE.txt:52-55`) | `notes/ABLETON-WINE-PUSH2-DISPLAY.md` | | 0033 WINE_DISABLE_UNIX_MOUNT_REPARSE | Reports Unix mount points as plain directories; Live's browser omitted folders behind unresolvable junctions | correctness | From ENCORE (`patches/BASE.txt:56-58`); experimental environment-variable workaround | `notes/ABLETON-WINE-ENCORE-REVIEW.md` | -| 0062 keep a selected top-level class offscreen | `WINE_X11_FORCE_OFFSCREEN_CLASS` pins Live's main window class on the offscreen path; M4L track selection no longer unmaps/reparents the whole client (black flash) | stability | Local; experimental — environment variable with an exact class name | `notes/ABLETON-WINE-M4L-SELECTION-FLICKER.md` | +| 0062 keep a selected top-level class offscreen | `WINE_X11_FORCE_OFFSCREEN_CLASS` pins Live's main window class on the offscreen path; M4L track selection no longer unmaps/reparents the whole client (black flash) | stability | Local; experimental (environment variable with an exact class name) | `notes/ABLETON-WINE-M4L-SELECTION-FLICKER.md` | ## Misc: shell, OLE, and build maintenance @@ -167,9 +167,9 @@ These systems already cover performance ground without being Wine patches. Later | System | What it does | Evidence | |---|---|---| -| ntsync (kernel fast path for NT synchronization) | The build vendors `linux/ntsync.h` and fails if either runtime half is missing. Without it every NT wait crosses wineserver: ~45% of one core idle and ~9,000 context switches/s; restoring it gave 4–50× synchronization throughput | `notes/ABLETON-WINE-NTSYNC-REGRESSION.md:3-14`, `vendor/ntsync-uapi/` | +| ntsync (kernel fast path for NT synchronization) | The build vendors `linux/ntsync.h` and fails if either runtime half is missing. Without it every NT wait crosses wineserver: ~45% of one core idle and ~9,000 context switches/s; restoring it gave 4-50× synchronization throughput | `notes/ABLETON-WINE-NTSYNC-REGRESSION.md:3-14`, `vendor/ntsync-uapi/` | | Real-time scheduling | The launcher starts Wine under `SCHED_RR` priority 10 when permitted; PipeASIO requests `SCHED_FIFO` 15 for its data-loop thread | `notes/ABLETON-WINE-RT-SCHEDULING.md:3-5,16-17` | -| Live GPU renderer enablement | `setup-prefix.sh` removes `-_ForceGdiBackend` so Live uses its Direct2D/D3D11 renderer; idle CPU dropped from ~59% of one core to 1–2% | `notes/ABLETON-WINE-GPU-RENDERER.md:8-9,28-29` | +| Live GPU renderer enablement | `setup-prefix.sh` removes `-_ForceGdiBackend` so Live uses its Direct2D/D3D11 renderer; idle CPU dropped from ~59% of one core to 1-2% | `notes/ABLETON-WINE-GPU-RENDERER.md:8-9,28-29` | | PipeASIO driver | Native PipeWire ASIO client; replaced WineASIO and removed JACK from Live's audio path in release 2026.07.17.2 | `notes/ABLETON-WINE-PIPEASIO.md:3-4` | | Ableton Link | Live joins Link through Wine's unmodified network stack; a native daemon (`tools/ableton-linkd.cpp`) holds the session across restarts | `notes/ABLETON-WINE-LINK-FIRSTCLASS.md:11`, `notes/ABLETON-WINE-LINK.md` | | APC coalescing fix | Proposal only. Unimplemented: the `-DontCombineAPCs` experiment was reverted and current releases strip it | `notes/ABLETON-WINE-APC-COALESCING.md:9,77-79` | diff --git a/notes/performance-moonshot/docs/PROJECT-TECHNOLOGIES.md b/notes/performance-moonshot/docs/PROJECT-TECHNOLOGIES.md index cc074abf..2f579aec 100644 --- a/notes/performance-moonshot/docs/PROJECT-TECHNOLOGIES.md +++ b/notes/performance-moonshot/docs/PROJECT-TECHNOLOGIES.md @@ -354,14 +354,14 @@ DSP load is Live's own audio-engine utilization meter. beta/scripts/ableton-linux-profiler.sh:1-3). - `tools/` holds about 40 diagnostic utilities: PE probes built like Wine's own PE modules (swamprobe, liveinject, midihot, linkprobe, mousespy, - setsyscolors, learnheal, webviewclose, metricprobe2 — the nine with + setsyscolors, learnheal, webviewclose, metricprobe2; the nine with `build_*.sh` scripts), plus X11-side helpers (xmon, xdrag, xclose, ukey, uidrag) and one-off probes without build scripts. `tools/m4l-hang-capture.sh` and `tools/m4l-font-audit.py` target Max for Live stalls. -Why it matters: the bench harness defines the project's evidence standard — -every moonshot claim needs a committed before/after pair — but the automated -metrics cover only wined3d_cs CPU and wineserver context switches. +Why it matters: the bench harness defines the project's evidence standard +(every moonshot claim needs a committed before/after pair), but the +automated metrics cover only wined3d_cs CPU and wineserver context switches. ## Gaps and unknowns diff --git a/notes/performance-moonshot/docs/PROTON-GRAPHICS-AND-RUNTIME.md b/notes/performance-moonshot/docs/PROTON-GRAPHICS-AND-RUNTIME.md index 65877021..cfdd32ae 100644 --- a/notes/performance-moonshot/docs/PROTON-GRAPHICS-AND-RUNTIME.md +++ b/notes/performance-moonshot/docs/PROTON-GRAPHICS-AND-RUNTIME.md @@ -55,7 +55,7 @@ Facts about the current stack, from this repository: was an X11 depth mismatch, not a translation-layer problem (`notes/ABLETON-WINE-GL-PLUGIN-EDITOR-CRASH-BUG.md:21-36`). - WebView2 plugin editors (Splice INSTRUMENT) crash on an OLE drag-drop - pointer, fixed in ole32 by patch 0045 — again not a graphics problem + pointer, fixed in ole32 by patch 0045, again not a graphics problem (`notes/ABLETON-WINE-WEBVIEW2-PLUGIN-CLOSE-CRASH.md:10-24`). - The audio engine does not touch Wine's audio drivers: it runs ASIO through PipeASIO into PipeWire (`scripts/ableton-live:760-762`, @@ -79,8 +79,8 @@ principle replace the wined3d-GL translation. wined3d serializes rendering on one command-stream thread (the "CS thread" of `notes/ABLETON-WINE-GPU-RENDERER.md:108-116`); DXVK's submission model is more parallel, and its per-frame CPU cost is lower for heavy D3D11 -workloads. Live's UI is not a heavy D3D11 workload — it is a 2D desktop UI -— so the expected gain is smaller than in games and may be near zero. +workloads. Live's UI is not a heavy D3D11 workload (it is a 2D desktop +UI), so the expected gain is smaller than in games and may be near zero. Risks are concrete, not theoretical: @@ -100,8 +100,8 @@ Risks are concrete, not theoretical: Cheaper experiment first: wined3d has its own Vulkan backend, selectable without swapping DLLs. Unverified: whether that backend works with Live's D2D usage and with the dcomp patches (0041). Either way, the test is a -prefix-level A/B — the vendored winetricks already carries dxvk verbs -(`vendor/winetricks:6887-6900`) — and does not require a rebuild. +prefix-level A/B (the vendored winetricks already carries dxvk verbs, +`vendor/winetricks:6887-6900`) and does not require a rebuild. ## vkd3d-proton @@ -120,7 +120,7 @@ editor uses D3D12. Unverified: whether any future plugin editor (game-engine DXVK-NVAPI implements NVIDIA's NVAPI (driver feature library: GPU queries, Reflex latency reduction, DLSS hooks) on top of DXVK/Vulkan. It is actively -maintained — 0.9.1 shipped 2026-01 and 0.9.2 followed in 2026-05 with +maintained: 0.9.1 shipped 2026-01 and 0.9.2 followed in 2026-05 with experimental D3D12 shader extensions ([Phoronix Linux gaming archive](https://www.phoronix.com/linux/Linux+Gaming), [dxvk-nvapi releases](https://github.com/jp7677/dxvk-nvapi/releases)). @@ -225,10 +225,10 @@ GE-Proton adds, on top of Valve's Proton | Addition | Purpose | Applies to a DAW? | |---|---|---| -| Media Foundation patches | game video cutscenes | Only marginally — Live's media import already works through winegstreamer (`scripts/container-build.sh:96-102`) | +| Media Foundation patches | game video cutscenes | Only marginally. Live's media import already works through winegstreamer (`scripts/container-build.sh:96-102`) | | `WINE_FULLSCREEN_FSR` upscaling | game rendering | No | | NVIDIA CUDA / PhysX / NVAPI | game physics, DLSS | No | -| Raw input patches | game mouse input | No — MIDI and mouse already work; see `notes/ABLETON-WINE-INPUT-BUG.md` for the actual input work | +| Raw input patches | game mouse input | No. MIDI and mouse already work; see `notes/ABLETON-WINE-INPUT-BUG.md` for the actual input work | | protonfixes per-game fixes | game-specific hacks | The pattern applies (this repo's patch series is the same idea), the content does not | | wine-staging backports | assorted | Case by case; this repo already curates its own series | | NTSync enablement | synchronization | Already done here (`notes/ABLETON-WINE-NTSYNC-REGRESSION.md:50-56`) | @@ -242,7 +242,7 @@ umu with the full container environment (same README). pressure-vessel is the container launcher that runs Proton against Steam Linux Runtime, a fixed library set, so games see identical libraries on every distribution ([Valve's steam-runtime known-issues doc](https://github.com/ValveSoftware/steam-runtime/blob/master/doc/steamlinuxruntime-known-issues.md)). -It is actively developed — Steam Linux Runtime 3.0 (sniper) was updated in +It is actively developed: Steam Linux Runtime 3.0 (sniper) was updated in 2026-06 including arm64 pressure-vessel builds ([SteamDB patch notes, 2026-06-03](https://steamdb.info/patchnotes/23181402/)). @@ -268,8 +268,8 @@ timing lets an app schedule exactly when a frame lands. For a DAW, the latency that matters is the audio callback deadline, which none of this touches. UI frame latency affects feel, not correctness. This -project's own present-path work — the direct GL present of patch 0055 with -the 0058/0059 gates — is the equivalent optimization, already done and +project's own present-path work (the direct GL present of patch 0055 with +the 0058/0059 gates) is the equivalent optimization, already done and measured (`notes/ABLETON-WINE-GPU-RENDERER.md:158-171`). Present timing only exists for Vulkan, so it becomes relevant only if the DXVK experiment lands. Gamescope does not apply: Live is a multi-window desktop app with diff --git a/notes/performance-moonshot/docs/PROTON-SYNC-AND-CPU.md b/notes/performance-moonshot/docs/PROTON-SYNC-AND-CPU.md index b7f7f7be..c98627e1 100644 --- a/notes/performance-moonshot/docs/PROTON-SYNC-AND-CPU.md +++ b/notes/performance-moonshot/docs/PROTON-SYNC-AND-CPU.md @@ -31,7 +31,7 @@ synchronization throughput 4 to 50 times (notes/ABLETON-WINE-NTSYNC-REGRESSION.md:10-14). ntsync removes *handle waits* from wineserver, but not everything: -alertable sleeps and APC (asynchronous procedure call — a callback Windows +alertable sleeps and APC (asynchronous procedure call: a callback Windows queues onto a specific thread) delivery still go through the server (notes/ABLETON-WINE-APC-COALESCING.md:14-18). Live's APC coalescing thread idles at 30 to 40% of one core, and the single-threaded wineserver @@ -126,7 +126,7 @@ waits in-process through the device instead of calling wineserver. | Container build injects header | Containerfile:94-98 | | Build fails if configure misses it | scripts/container-build.sh:104-125 (`HAVE_LINUX_NTSYNC_H`, both runtime halves) | | Installed-runtime verification | scripts/check-ntsync.sh:31-38 (static gate), :68-71 (server must open `/dev/ntsync` when it exists) | -| Semantics + throughput probe | beta/tester-kit/probes/src/ntsyncprobe.c:1-10; runs A–D table in notes/ABLETON-WINE-NTSYNC-REGRESSION.md:38-43 | +| Semantics + throughput probe | beta/tester-kit/probes/src/ntsyncprobe.c:1-10; runs A-D table in notes/ABLETON-WINE-NTSYNC-REGRESSION.md:38-43 | | Regression history | builds 2026-07-12/14 silently lost ntsync; fixed 2026.07.17.1 (notes/ABLETON-WINE-NTSYNC-REGRESSION.md:1-6) | One host-side gap remains: the driver needs `/dev/ntsync` to exist, which @@ -140,7 +140,7 @@ has the module unloaded. list, and it is already in. Live runs many worker threads with many short waits at audio-period rates (a 256-frame buffer at 48 kHz is a 5.3 ms cycle). The repo's probe measured event ping-pong rising from ~75k to -327–392k round trips/s and semaphore churn from ~64k to 3.3–3.6M pairs/s +327-392k round trips/s and semaphore churn from ~64k to 3.3-3.6M pairs/s with ntsync active (notes/ABLETON-WINE-NTSYNC-REGRESSION.md:38-43). Lower per-wait latency and lower wineserver load directly protect audio deadlines. Remaining limit: alertable sleeps/APC delivery still use @@ -150,7 +150,7 @@ wineserver (notes/ABLETON-WINE-APC-COALESCING.md:14-18). *What it does:* `WINE_CPU_TOPOLOGY=N:cpu,cpu,...` is a Proton-side Wine patch that caps and remaps the logical processors reported to the Windows -app — used to fix games that break on high core counts and to pin games +app: used to fix games that break on high core counts and to pin games onto specific cores (e.g. the V-Cache CCD on Ryzen X3D parts) (https://github.com/ValveSoftware/Proton/issues/7719, https://github.com/CachyOS/proton-cachyos/issues/178). Proton ships @@ -160,8 +160,8 @@ Feral GameMode parking non-cache cores on hybrid CPUs so the game stays on the fast ones (https://github.com/ValveSoftware/Proton/issues/8075; GameMode project: https://github.com/FeralInteractive/gamemode). -*In this repo:* groundwork only. The launcher computes a sensible value — -cap 8 CPUs, honor `taskset`/cgroup restrictions, user override wins — and +*In this repo:* groundwork only. The launcher computes a sensible value +(cap 8 CPUs, honor `taskset`/cgroup restrictions, user override wins) and exports it (scripts/ableton-live:75-108), but the code is explicit: "Inert on this runtime until the patched ntdll/wineserver consumer lands; exported as groundwork only" (scripts/ableton-live:78-79). The consumer patch is @@ -189,7 +189,7 @@ under `SCHED_RR` priority 10 when `chrt -r 10 true` succeeds (scripts/setup-realtime.sh:74); PipeASIO separately requests SCHED_FIFO 15 for its data loop (notes/ABLETON-WINE-RT-SCHEDULING.md:1-7). Boosting wineserver itself with `chrt -f -p 95` was considered and deliberately left -out — it needs root per launch, and raising a single-threaded server above +out: it needs root per launch, and raising a single-threaded server above its callers can invert the contention it means to fix (scripts/setup-realtime.sh:23-25). The known open risk: Live's realtime threads make synchronous calls into a `SCHED_OTHER` wineserver, a classic @@ -238,8 +238,8 @@ remaining wineserver traffic cheaper or rarer" is. Games and DAWs differ in one way that matters: a game can drop a frame; a DAW cannot miss a buffer. The sync techniques transfer directly because -Live's thread pattern — many workers, many short waits, hard periodic -deadlines — is the pattern ntsync was benchmarked on, and this repo already +Live's thread pattern (many workers, many short waits, hard periodic +deadlines) is the pattern ntsync was benchmarked on, and this repo already has the wins and the guards. The remaining moonshot surface is the traffic ntsync does not cover (alertable waits, APC delivery), the priority relationship between Live's RT threads and wineserver, and CPU-count/affinity @@ -248,40 +248,40 @@ there is worth resurrecting. ## Key opportunities -1. **Close the `/dev/ntsync` host gap** — detect a kernel ≥ 6.14 with the +1. **Close the `/dev/ntsync` host gap**: detect a kernel ≥ 6.14 with the ntsync module unloaded and tell the user exactly how to load it (or ship a `modprobe.d`/udev drop-in via setup scripts). Impact: high (users on - qualifying kernels silently lose 4–50x sync throughput otherwise). + qualifying kernels silently lose 4-50x sync throughput otherwise). Effort: low. Evidence: scripts/check-ntsync.sh:38,68-71 checks but does not remediate; notes/ABLETON-WINE-NTSYNC-REGRESSION.md:38-43 quantifies the loss. -2. **Port Proton's `WINE_CPU_TOPOLOGY` consumer patch into the runtime** — +2. **Port Proton's `WINE_CPU_TOPOLOGY` consumer patch into the runtime**: the launcher already computes and exports the value; the ntdll/wineserver consumer is the missing half. Impact: medium (fixes worker-pool oversizing on >8-core hosts; enables V-Cache/P-core pinning). Effort: medium. Evidence: scripts/ableton-live:75-108, explicit "Inert … groundwork only" at scripts/ableton-live:78-79. -3. **Run the deferred wineserver priority/affinity A/B** — test +3. **Run the deferred wineserver priority/affinity A/B**: test `chrt -f` boost and/or CPU affinity for wineserver under playback load using the existing `bench-run.sh` harness; the boost is documented as deliberately excluded pending measurement, not as rejected. Impact: medium (targets the Live-RT-thread vs SCHED_OTHER-wineserver inversion). Effort: low. Evidence: scripts/setup-realtime.sh:23-25, notes/ABLETON-WINE-RT-SCHEDULING.md:39-41,43-81. -4. **Attack the alertable-wait/APC wineserver path** — ntsync does not +4. **Attack the alertable-wait/APC wineserver path**: ntsync does not cover alertable sleeps or APC delivery; Live's APC coalescing thread - burns 30–40% of a core idle, and per-APC wineserver serialization is the + burns 30-40% of a core idle, and per-APC wineserver serialization is the current fault hypothesis under load. Impact: high. Effort: high. Evidence: notes/ABLETON-WINE-APC-COALESCING.md:1-6,14-28. 5. **Verify the upstream non-ntsync in-process fallback and, if real, - enable it in builds** — Unverified: community sources say upstream + enable it in builds**. Unverified: community sources say upstream Wine ≥ 10.15 has an eventfd in-process fallback when `/dev/ntsync` is absent; this repo's header-less fallback builds paid full wineserver round trips. If the fallback is compile-gated on the same header, no action; if not, users on kernels < 6.14 get a free win. Impact: medium. Effort: low. Evidence: https://github.com/AdelKS/LinuxGamingGuide vs notes/ABLETON-WINE-NTSYNC-REGRESSION.md:38-46. -6. **Publish hybrid-CPU affinity guidance** — document `taskset` / +6. **Publish hybrid-CPU affinity guidance**: document `taskset` / GameMode-style core selection for Intel P/E and Ryzen X3D hosts running Live, matching what Proton users already do per game. Impact: low to medium. Effort: low. Evidence: diff --git a/patches/SERIES.sha256 b/patches/SERIES.sha256 index 2f3f32f1..860cc16c 100644 --- a/patches/SERIES.sha256 +++ b/patches/SERIES.sha256 @@ -66,4 +66,4 @@ f25d5b4c3ee71b7e9491f91c462e2b7ddaca377b8b94c7bd95df7734f0a4b563 pipeasio/0002- 1c29e4021184dff178914b74d07d28d4bffc2891ea8cba789f075ed9fb375af0 pipeasio/0004-accept-any-buffer-size-in-range-log-every-adjustment.patch f94c3410be18fb2677993081f47122ba06246ee768b5fcbe1993be0e9e12f44e pipeasio/0005-converge-on-a-foreign-quantum-predict-adopt-mute.patch 5edfaf6c143e206902ce31772912b2ebc2fff3b0ae8db9c1b60d6239abdd1ccc pipeasio/0006-anchor-fallback-capture-report-clock-domains.patch -6bca950dc7188358c09c89438417bebe2848fe74855326b2b9b8255b93ec56ea pipeasio/0007-give-the-follower-device-buffer-room.patch +1353cb590149b368a1892c3682df7acab71a1eff4bb451fa98d8abcb9a82d72a pipeasio/0007-give-the-follower-device-buffer-room.patch diff --git a/patches/pipeasio/0007-give-the-follower-device-buffer-room.patch b/patches/pipeasio/0007-give-the-follower-device-buffer-room.patch index dd533a09..ae6ae055 100644 --- a/patches/pipeasio/0007-give-the-follower-device-buffer-room.patch +++ b/patches/pipeasio/0007-give-the-follower-device-buffer-room.patch @@ -1,7 +1,7 @@ diff --git a/src/audio.c b/src/audio.c --- a/src/audio.c +++ b/src/audio.c -@@ -70,7 +70,10 @@ +@@ -70,16 +70,21 @@ #include #include #include @@ -10,9 +10,20 @@ diff --git a/src/audio.c b/src/audio.c +#include +#include ++#include #include #include -@@ -269,6 +272,19 @@ + #include + #include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -269,6 +274,19 @@ /* Opaque types backing audio.h. */ @@ -32,7 +43,7 @@ diff --git a/src/audio.c b/src/audio.c struct audio_client { char *name; -@@ -354,6 +370,20 @@ +@@ -354,6 +372,20 @@ * puts the clock topology in the log for when it is not (C8). */ uint32_t chosen_node[2]; uint32_t warned_clock_pair[2]; @@ -53,7 +64,7 @@ diff --git a/src/audio.c b/src/audio.c }; struct audio_node_info -@@ -407,6 +437,10 @@ +@@ -407,6 +439,10 @@ .process = audio_on_process, }; @@ -64,37 +75,71 @@ diff --git a/src/audio.c b/src/audio.c /* Core and registry event forward declarations. */ static void audio_on_core_done(void *userdata, uint32_t id, int seq); -@@ -454,6 +488,29 @@ +@@ -454,6 +490,63 @@ c->our_node_id = SPA_ID_INVALID; c->chosen_node[0] = c->chosen_node[1] = SPA_ID_INVALID; c->warned_clock_pair[0] = c->warned_clock_pair[1] = SPA_ID_INVALID; + c->hr_slot[0].node = c->hr_slot[1].node = SPA_ID_INVALID; + + /* How many frames of buffer room the second device of a two-device -+ * session is guaranteed. Unset: 512. "off" or "0": leave devices -+ * alone. Other values are kept between 16 and 8192, with a message -+ * when the given value is outside that range. */ ++ * session is guaranteed. Unset: 512. "off", "0", values below 1, ++ * and text that is not a number: leave devices alone. Positive values ++ * outside 16..8192 are clamped to that range, with a message. */ + const char *hr_env = getenv("PIPEASIO_FOLLOWER_HEADROOM"); + if (!hr_env) ++ { + c->hr_target = 512; -+ else if (!strcmp(hr_env, "off") || !strcmp(hr_env, "0")) -+ c->hr_target = -1; ++ } + else + { -+ long v = strtol(hr_env, NULL, 10); -+ if (v < 16) -+ v = 16; -+ if (v > 8192) -+ v = 8192; -+ if (v != atol(hr_env)) -+ WARN("PIPEASIO_FOLLOWER_HEADROOM=%s is outside 16..8192; using %ld\n", hr_env, -+ v); -+ c->hr_target = (int32_t)v; ++ /* Trim by span instead of copying into a fixed buffer: truncating an ++ * invalid long value could otherwise turn it into a valid prefix. */ ++ const char *beg = hr_env; ++ while (isspace((unsigned char)*beg)) ++ beg++; ++ const char *end = beg + strlen(beg); ++ while (end > beg && isspace((unsigned char)end[-1])) ++ end--; ++ ++ if (end == beg || (end - beg == 3 && !strncasecmp(beg, "off", 3))) ++ c->hr_target = -1; ++ else ++ { ++ char *num_end; ++ errno = 0; ++ long v = strtol(beg, &num_end, 10); ++ while (num_end < end && isspace((unsigned char)*num_end)) ++ num_end++; ++ if (num_end != end) ++ { ++ WARN("PIPEASIO_FOLLOWER_HEADROOM=%s is not a frame count; turned off\n", ++ hr_env); ++ c->hr_target = -1; ++ } ++ else if (v <= 0) ++ c->hr_target = -1; ++ else ++ { ++ if (v < 16) ++ { ++ WARN("PIPEASIO_FOLLOWER_HEADROOM=%s is below 16; using 16\n", ++ hr_env); ++ v = 16; ++ } ++ else if (errno == ERANGE || v > 8192) ++ { ++ WARN("PIPEASIO_FOLLOWER_HEADROOM=%s is above 8192; using 8192\n", ++ hr_env); ++ v = 8192; ++ } ++ c->hr_target = (int32_t)v; ++ } ++ } + } #ifndef PIPEASIO_AUDIO_UNIXLIB atomic_init(&c->rt.ready, false); #endif -@@ -514,6 +571,19 @@ +@@ -514,6 +607,19 @@ if (c->registry) pw_registry_add_listener(c->registry, &c->registry_listener, &audio_registry_events, c); @@ -114,7 +159,7 @@ diff --git a/src/audio.c b/src/audio.c pw_thread_loop_unlock(c->loop); audio_sync(c); /* A second sync drains the "default" metadata's initial property burst: -@@ -551,6 +621,19 @@ +@@ -551,6 +657,19 @@ if (c->active) audio_teardown_filter(c); @@ -134,7 +179,7 @@ diff --git a/src/audio.c b/src/audio.c if (c->loop) { pw_thread_loop_lock(c->loop); -@@ -1160,6 +1243,211 @@ +@@ -1160,6 +1279,239 @@ cn ? cn->node_name : "?", pn ? pn->node_name : "?"); } @@ -148,9 +193,11 @@ diff --git a/src/audio.c b/src/audio.c + * follower's spare room (the ALSA property "api.alsa.headroom"), and put + * the old value back when the session ends or the devices change. The + * property can be changed on a live node through its Props parameter; -+ * verified against PipeWire 1.6.8. Sessions on one device are never -+ * touched. PIPEASIO_FOLLOWER_HEADROOM picks the amount or turns this -+ * off. */ ++ * verified against PipeWire 1.6.8. The one-reply cap this used to put ++ * on the Props enumeration hid the ALSA device's reply on PipeWire ++ * 1.5.85 (stock Pop!_OS 24.04), which is why the enumeration below ++ * asks for 16. Sessions on one device are never touched. ++ * PIPEASIO_FOLLOWER_HEADROOM picks the amount or turns this off. */ + +/* Send a device node a new buffer-room value. */ +static void @@ -305,12 +352,29 @@ diff --git a/src/audio.c b/src/audio.c + s->node = want[w]; + pw_node_add_listener((struct pw_node *)s->proxy, &s->listener, + &audio_hr_node_events, s); -+ pw_node_enum_params((struct pw_node *)s->proxy, 0, SPA_PARAM_Props, 0, 1, ++ /* Ask for up to 16 replies, not one: behind WirePlumber's ++ * audio adapter the adapter's own Props object arrives first, ++ * and the ALSA device's object (with clock.name and ++ * api.alsa.headroom) comes after it; the param handler above ++ * accumulates across these events. */ ++ pw_node_enum_params((struct pw_node *)s->proxy, 0, SPA_PARAM_Props, 0, 16, + NULL); + break; + } + } + ++ /* When neither device sets the timing (the graph clock is a dummy ++ * driver), both look like followers; the message below then names the ++ * real clock instead of claiming each device follows the other. */ ++ bool any_sets_timing = false; ++ for (int i = 0; i < 2; i++) ++ { ++ struct audio_hr_slot *s = &c->hr_slot[i]; ++ if (s->node != SPA_ID_INVALID && s->probed && s->clock_name[0] ++ && !strcmp(s->clock_name, gclock)) ++ any_sets_timing = true; ++ } ++ + /* Raise the device that is not setting the timing; undo the raise if + * that role later lands on a device we raised. */ + for (int i = 0; i < 2; i++) @@ -325,11 +389,20 @@ diff --git a/src/audio.c b/src/audio.c + s->prev = s->cur_headroom; + s->raised = true; + audio_hr_set(s->proxy, c->hr_target); -+ WARN("two audio devices are in use, and \"%s\" follows the other " -+ "one's clock; it got %d frames of buffer room (was %d) so " -+ "the adjustment stays silent (pipeasio-follower-headroom). " -+ "PIPEASIO_FOLLOWER_HEADROOM changes or disables this.\n", -+ n ? n->node_name : "?", c->hr_target, s->prev); ++ if (any_sets_timing) ++ WARN("two audio devices are in use, and \"%s\" follows the " ++ "other one's clock; it got %d frames of buffer room " ++ "(was %d) so the adjustment stays silent " ++ "(pipeasio-follower-headroom). PIPEASIO_FOLLOWER_HEADROOM " ++ "changes or disables this.\n", ++ n ? n->node_name : "?", c->hr_target, s->prev); ++ else ++ WARN("two audio devices are in use, and \"%s\" follows the " ++ "graph clock \"%s\", not the other device; it got %d " ++ "frames of buffer room (was %d) so the adjustment stays " ++ "silent (pipeasio-follower-headroom). " ++ "PIPEASIO_FOLLOWER_HEADROOM changes or disables this.\n", ++ n ? n->node_name : "?", gclock, c->hr_target, s->prev); + } + else if (sets_timing && s->raised) + { @@ -346,7 +419,7 @@ diff --git a/src/audio.c b/src/audio.c const char ** audio_get_ports(audio_client_t *c, const char *port_name_pattern, const char *type_name_pattern, uint64_t flags) -@@ -1569,6 +1857,18 @@ +@@ -1569,6 +1893,18 @@ if (quantum) atomic_store(&c->observed_quantum, quantum); @@ -365,7 +438,7 @@ diff --git a/src/audio.c b/src/audio.c /* bufferSwitch and PipeWire must run at the same quantum. While they * disagree, mute: publish silence and zero the input, so the audible * fault is silence plus one log line per episode instead of wrong-speed -@@ -2062,6 +2362,9 @@ +@@ -2062,6 +2398,9 @@ c->chosen_node[0] = SPA_ID_INVALID; if (c->chosen_node[1] == id) c->chosen_node[1] = SPA_ID_INVALID; diff --git a/scripts/build-audit.sh b/scripts/build-audit.sh index b8848687..04a0f957 100755 --- a/scripts/build-audit.sh +++ b/scripts/build-audit.sh @@ -139,15 +139,17 @@ FINGERPRINTS=' 0063|ascii|lib/wine/x86_64-unix/comdlg32.so|org.freedesktop.FileManager1 0064|ascii|lib/wine/x86_64-unix/comdlg32.so|ShowFolders 0064|ascii|lib/wine/x86_64-windows/shell32.dll|__wine_portal_open_folder -pipeasio/0001|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-clamp-sample-rate -pipeasio/0002|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-midi-timebase -pipeasio/0003|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-quantum-arbitration -pipeasio/0004|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-any-buffer-size -pipeasio/0005|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-quantum-converge -pipeasio/0006|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-clock-domains -pipeasio/0007|ascii|lib/wine/x86_64-unix/pipeasio64.dll.so|pipeasio-follower-headroom +pipeasio/0001|ascii|lib/wine/x86_64-unix/pipeasio.dll.so|pipeasio-clamp-sample-rate +pipeasio/0002|ascii|lib/wine/x86_64-unix/pipeasio.dll.so|pipeasio-midi-timebase +pipeasio/0003|ascii|lib/wine/x86_64-unix/pipeasio.dll.so|pipeasio-quantum-arbitration +pipeasio/0004|ascii|lib/wine/x86_64-unix/pipeasio.dll.so|pipeasio-any-buffer-size +pipeasio/0005|ascii|lib/wine/x86_64-unix/pipeasio.dll.so|pipeasio-quantum-converge +pipeasio/0006|ascii|lib/wine/x86_64-unix/pipeasio.dll.so|pipeasio-clock-domains +pipeasio/0007|ascii|lib/wine/x86_64-unix/pipeasio.dll.so|pipeasio-follower-headroom ' # pipeasio's code is in the unix .so; the PE pipeasio64.dll is a codeless fake module. +# Wine loads the unix half under the spec-file name pipeasio.dll.so, so the +# fingerprints (and the readelf checks below) aim at that file. STAMP_ONLY=' 0002|logic-only (visible-rect gates; adds no string literal) 0004|logic-only (reentrant wpchanged state) @@ -261,14 +263,14 @@ if command -v readelf >/dev/null; then | grep -qF 'Shared library: [libusb-1.0.so.0]' \ && ok "libusb-1.0.so DT_NEEDED" "host libusb-1.0.so.0" \ || bad "libusb-1.0.so DT_NEEDED" "host libusb-1.0.so.0 not linked" - readelf -d "$tree/lib/wine/x86_64-unix/pipeasio64.dll.so" 2>/dev/null \ + readelf -d "$tree/lib/wine/x86_64-unix/pipeasio.dll.so" 2>/dev/null \ | grep -qF 'Shared library: [libpipewire-0.3.so.0]' \ - && ok "pipeasio64.dll.so DT_NEEDED" "host libpipewire-0.3.so.0" \ - || bad "pipeasio64.dll.so DT_NEEDED" "host libpipewire-0.3.so.0 not linked" - if readelf -d "$tree/lib/wine/x86_64-unix/pipeasio64.dll.so" 2>/dev/null | grep -qE 'RPATH|RUNPATH'; then - bad "pipeasio64.dll.so rpath" "carries a build-container rpath" + && ok "pipeasio.dll.so DT_NEEDED" "host libpipewire-0.3.so.0" \ + || bad "pipeasio.dll.so DT_NEEDED" "host libpipewire-0.3.so.0 not linked" + if readelf -d "$tree/lib/wine/x86_64-unix/pipeasio.dll.so" 2>/dev/null | grep -qE 'RPATH|RUNPATH'; then + bad "pipeasio.dll.so rpath" "carries a build-container rpath" else - ok "pipeasio64.dll.so rpath" "none (resolves via host loader)" + ok "pipeasio.dll.so rpath" "none (resolves via host loader)" fi readelf -d "$tree/lib/wine/x86_64-unix/winegstreamer.so" 2>/dev/null \ | grep -qF 'Shared library: [libgstreamer-1.0.so.0]' \ From 3496844b21443ed6f17c0805fcca703acdfcd36a Mon Sep 17 00:00:00 2001 From: Shibco Date: Fri, 7 Aug 2026 20:34:41 +0200 Subject: [PATCH 3/3] Address the final review suggestions --- TROUBLESHOOTING.md | 5 ++--- .../docs/AUDIO-LATENCY-ECOSYSTEM.md | 2 +- .../docs/OWN-FORK-BUILD-AND-RUNTIME.md | 2 +- patches/BASE.txt | 3 +++ patches/SERIES.sha256 | 2 +- ...give-the-follower-device-buffer-room.patch | 22 ++++++++++++------- 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 264a32e4..e1d5fa76 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -133,9 +133,8 @@ echo ntsync | sudo tee /etc/modules-load.d/90-ableton-ntsync.conf Restart Live. The startup warning no longer appears. -If the first command reports that the module does not exist, your kernel is -too old. Install your distribution's newest kernel (6.14 or newer) and run -the commands again. +If the first command reports that the module does not exist, move to a kernel +that provides ntsync (Linux 6.14 or newer) and run the commands again. ## Audio latency remains high diff --git a/notes/performance-moonshot/docs/AUDIO-LATENCY-ECOSYSTEM.md b/notes/performance-moonshot/docs/AUDIO-LATENCY-ECOSYSTEM.md index 528693f6..56bc3d87 100644 --- a/notes/performance-moonshot/docs/AUDIO-LATENCY-ECOSYSTEM.md +++ b/notes/performance-moonshot/docs/AUDIO-LATENCY-ECOSYSTEM.md @@ -89,7 +89,7 @@ rtkit (RealtimeKit) is a D-Bus service that grants realtime scheduling to unpriv This creates two failure modes relevant here. First, a user without `rtprio` limits falls back to rtkit, whose priority ceiling of 20 sits below the priorities JACK and PipeWire documentation assume; the pro-audio consensus is to prefer `RLIMIT_RTPRIO` through `limits.conf` (or the `realtime-privileges` package) over rtkit (https://github.com/rerdavies/pipedal/discussions/99). Second, yabridge documents the 200 ms RTTIME cap as a concrete cause of warnings and throttled audio threads, and notes GNOME 45+ forces this path (https://github.com/robbert-vdh/yabridge). -This repo's launcher needs `rtprio` to succeed at its `chrt -r 10` probe (`scripts/ableton-live:780-782`), and `scripts/setup-realtime.sh` installs that permission; the script deliberately leaves out a wineserver `chrt -f -p 95` boost because it needs root (`scripts/setup-realtime.sh:13-23`). The moonshot decision to make is whether the priority ladder (PipeWire data threads (rtkit/rt.prio), Live under RR 10, PipeASIO at FIFO 15, wineserver at `SCHED_OTHER`) is the right order. Today the component Live's realtime threads block on most, wineserver, has the lowest priority. That is a classic priority-inversion shape, already listed as an unconfirmed hypothesis in the RT note (`notes/ABLETON-WINE-RT-SCHEDULING.md:38-41`). +This repo's launcher needs `rtprio` to succeed at its `chrt -r 10` probe (`scripts/ableton-live:780-782`), and `scripts/setup-realtime.sh` installs that permission; the script deliberately leaves out a wineserver `chrt -f -p 95` boost because it needs root (`scripts/setup-realtime.sh:13-23`). The moonshot decision to make is whether the priority ladder—PipeWire data threads (rtkit/rt.prio), Live under RR 10, PipeASIO at FIFO 15, wineserver at `SCHED_OTHER`—is the right order. Today the component Live's realtime threads block on most, wineserver, has the lowest priority. That is a classic priority-inversion shape, already listed as an unconfirmed hypothesis in the RT note (`notes/ABLETON-WINE-RT-SCHEDULING.md:38-41`). ## JACK versus PipeWire for this use case diff --git a/notes/performance-moonshot/docs/OWN-FORK-BUILD-AND-RUNTIME.md b/notes/performance-moonshot/docs/OWN-FORK-BUILD-AND-RUNTIME.md index 02866360..504636a7 100644 --- a/notes/performance-moonshot/docs/OWN-FORK-BUILD-AND-RUNTIME.md +++ b/notes/performance-moonshot/docs/OWN-FORK-BUILD-AND-RUNTIME.md @@ -74,7 +74,7 @@ Absences and behaviors: | lowlatency / PREEMPT_RT kernel | advised only, for sub-256-frame buffers | `scripts/setup-realtime.sh:146-151` | | wineserver `chrt -f 95` boost | deliberately omitted (root per launch; priority inversion risk) | `scripts/setup-realtime.sh:23-25` | -At launch, the launcher probes `chrt -r 10 true` and runs the whole Wine process tree under `SCHED_RR` priority 10 when the probe succeeds (`scripts/ableton-live:780-783`). SCHED_RR is a realtime round-robin policy: GUI threads inherit it along with audio threads. PipeASIO separately requests `SCHED_FIFO` priority 15 for its data-loop thread (`notes/ABLETON-WINE-RT-SCHEDULING.md:3-7`). The same note lists the open risks (Linux throttles realtime tasks to 950 ms per second by default, all inherited threads share one RR priority, and Live's realtime threads outrank the `SCHED_OTHER` wineserver they make synchronous calls to (`notes/ABLETON-WINE-RT-SCHEDULING.md:31-41`)) and prescribes a pending 4-CPU A/B comparison before changing the default (`notes/ABLETON-WINE-RT-SCHEDULING.md:43-82`). That comparison is still unrun; no `bench/` directory or committed results exist in the repo. +At launch, the launcher probes `chrt -r 10 true` and runs the whole Wine process tree under `SCHED_RR` priority 10 when the probe succeeds (`scripts/ableton-live:780-783`). SCHED_RR is a realtime round-robin policy: GUI threads inherit it along with audio threads. PipeASIO separately requests `SCHED_FIFO` priority 15 for its data-loop thread (`notes/ABLETON-WINE-RT-SCHEDULING.md:3-7`). The same note lists the open risks—Linux throttles realtime tasks to 950 ms per second by default, all inherited threads share one RR priority, and Live's realtime threads outrank the `SCHED_OTHER` wineserver they make synchronous calls to (`notes/ABLETON-WINE-RT-SCHEDULING.md:31-41`)—and prescribes a pending 4-CPU A/B comparison before changing the default (`notes/ABLETON-WINE-RT-SCHEDULING.md:43-82`). That comparison is still unrun; no `bench/` directory or committed results exist in the repo. rtkit (a D-Bus service that grants realtime scheduling to unprivileged clients) is not referenced anywhere in the repo. PipeWire uses it for its own data loops on hosts where it is installed; this project neither requires nor verifies it. diff --git a/patches/BASE.txt b/patches/BASE.txt index a10785e8..7e231449 100644 --- a/patches/BASE.txt +++ b/patches/BASE.txt @@ -20,6 +20,9 @@ Run `./build.sh`. Inside the Podman image, `scripts/container-build.sh` unpacks `vendor/wine-base-5c23dd1c.tar.zst`, creates a Git repository, and applies `patches/00*.patch` in lexical order with `git am --3way`. +It separately unpacks `vendor/pipeasio-1.2.2.tar.gz` and applies +`patches/pipeasio/*.patch` in lexical order with `patch -p1`. + Some early patch files have no `From` or `Date` header. The build script adds a fixed identity and date before passing those files to `git am`. A bare `git am patches/00*.patch` command does not reproduce this step. diff --git a/patches/SERIES.sha256 b/patches/SERIES.sha256 index 860cc16c..79d543cf 100644 --- a/patches/SERIES.sha256 +++ b/patches/SERIES.sha256 @@ -66,4 +66,4 @@ f25d5b4c3ee71b7e9491f91c462e2b7ddaca377b8b94c7bd95df7734f0a4b563 pipeasio/0002- 1c29e4021184dff178914b74d07d28d4bffc2891ea8cba789f075ed9fb375af0 pipeasio/0004-accept-any-buffer-size-in-range-log-every-adjustment.patch f94c3410be18fb2677993081f47122ba06246ee768b5fcbe1993be0e9e12f44e pipeasio/0005-converge-on-a-foreign-quantum-predict-adopt-mute.patch 5edfaf6c143e206902ce31772912b2ebc2fff3b0ae8db9c1b60d6239abdd1ccc pipeasio/0006-anchor-fallback-capture-report-clock-domains.patch -1353cb590149b368a1892c3682df7acab71a1eff4bb451fa98d8abcb9a82d72a pipeasio/0007-give-the-follower-device-buffer-room.patch +f8ddae79ec61912ae92af14598f39cce66a69c666bc39f4b3ea23aa865742861 pipeasio/0007-give-the-follower-device-buffer-room.patch diff --git a/patches/pipeasio/0007-give-the-follower-device-buffer-room.patch b/patches/pipeasio/0007-give-the-follower-device-buffer-room.patch index ae6ae055..f15c0d5c 100644 --- a/patches/pipeasio/0007-give-the-follower-device-buffer-room.patch +++ b/patches/pipeasio/0007-give-the-follower-device-buffer-room.patch @@ -75,7 +75,7 @@ diff --git a/src/audio.c b/src/audio.c /* Core and registry event forward declarations. */ static void audio_on_core_done(void *userdata, uint32_t id, int seq); -@@ -454,6 +490,63 @@ +@@ -454,6 +490,68 @@ c->our_node_id = SPA_ID_INVALID; c->chosen_node[0] = c->chosen_node[1] = SPA_ID_INVALID; c->warned_clock_pair[0] = c->warned_clock_pair[1] = SPA_ID_INVALID; @@ -117,7 +117,12 @@ diff --git a/src/audio.c b/src/audio.c + c->hr_target = -1; + } + else if (v <= 0) ++ { ++ if (v < 0) ++ WARN("PIPEASIO_FOLLOWER_HEADROOM=%s is not a frame count; turned off\n", ++ hr_env); + c->hr_target = -1; ++ } + else + { + if (v < 16) @@ -139,7 +144,7 @@ diff --git a/src/audio.c b/src/audio.c #ifndef PIPEASIO_AUDIO_UNIXLIB atomic_init(&c->rt.ready, false); #endif -@@ -514,6 +607,19 @@ +@@ -514,6 +612,19 @@ if (c->registry) pw_registry_add_listener(c->registry, &c->registry_listener, &audio_registry_events, c); @@ -159,7 +164,7 @@ diff --git a/src/audio.c b/src/audio.c pw_thread_loop_unlock(c->loop); audio_sync(c); /* A second sync drains the "default" metadata's initial property burst: -@@ -551,6 +657,19 @@ +@@ -551,6 +662,19 @@ if (c->active) audio_teardown_filter(c); @@ -179,7 +184,7 @@ diff --git a/src/audio.c b/src/audio.c if (c->loop) { pw_thread_loop_lock(c->loop); -@@ -1160,6 +1279,239 @@ +@@ -1160,6 +1284,240 @@ cn ? cn->node_name : "?", pn ? pn->node_name : "?"); } @@ -370,8 +375,9 @@ diff --git a/src/audio.c b/src/audio.c + for (int i = 0; i < 2; i++) + { + struct audio_hr_slot *s = &c->hr_slot[i]; -+ if (s->node != SPA_ID_INVALID && s->probed && s->clock_name[0] -+ && !strcmp(s->clock_name, gclock)) ++ if (s->node == SPA_ID_INVALID || !s->probed || !s->clock_name[0]) ++ return; ++ if (!strcmp(s->clock_name, gclock)) + any_sets_timing = true; + } + @@ -419,7 +425,7 @@ diff --git a/src/audio.c b/src/audio.c const char ** audio_get_ports(audio_client_t *c, const char *port_name_pattern, const char *type_name_pattern, uint64_t flags) -@@ -1569,6 +1893,18 @@ +@@ -1569,6 +1899,18 @@ if (quantum) atomic_store(&c->observed_quantum, quantum); @@ -438,7 +444,7 @@ diff --git a/src/audio.c b/src/audio.c /* bufferSwitch and PipeWire must run at the same quantum. While they * disagree, mute: publish silence and zero the input, so the audible * fault is silence plus one log line per episode instead of wrong-speed -@@ -2062,6 +2398,9 @@ +@@ -2062,6 +2404,9 @@ c->chosen_node[0] = SPA_ID_INVALID; if (c->chosen_node[1] == id) c->chosen_node[1] = SPA_ID_INVALID;