perf: defer native chrome repaint during sound callback slices - #1597
Merged
benletchford merged 1 commit intoSep 11, 2026
Merged
Conversation
This was referenced Sep 8, 2026
Merged
perf(trace): reuse trace-code proofs under embedder publication generations
benletchford/m68k-rs#174
Open
benletchford
force-pushed
the
perf/defer-sound-chrome-pr
branch
from
September 11, 2026 06:50
a7689d1 to
fec3dca
Compare
Owner
|
Rebased onto current master, preserving the current callback scheduling changes, and flattened the branch to a single conventional commit. The two-file scope is unchanged. Fresh local full-library validation: 5,427 passed, 0 failed, 3 ignored. New-head CI is running; this does not claim refreshed performance measurements. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
How this fits with the other performance work
The font/retained-text overhaul, Systemless #1524, merged in 0.39.0. The effect sizes below are SC2K process CPU time for the stated replay, not GUI Activity Monitor percentages or a cross-workload ranking.
Draft #1598: retained-pixel hashing is stacked on #1597, not included in any of the five PRs above. It measured about31.2% additional SC2K CPU reduction on0.39.0 and31.6–40.4% on the refreshed0.39.1 stack; both are after #1524. Clock/load variation means that wider range is not evidence that0.39.1 made the patch better. EV is not cleared: its unrestricted current pair used2.369% more CPU; a severely throttled reverse pair is excluded. The PR remains draft for that check. A separate local bulk retained-fill prototype has no measured effect size yet. Sequential reductions apply to remaining work, not by adding percentages; there is no directly measured all-PR total.
What the text-rendering overhaul changed
#1524 added CPU-side retained high-resolution glyph coverage alongside guest framebuffer bytes. Glyph masks are cached: the new hot work is repeatedly erasing/repainting their pixels and updating subpixel, palette and snapshot state, not necessarily rasterizing font outlines again. GPU presentation does not remove this bookkeeping.
That changes what each optimization can save. #1421 avoids rebuilding themed background artwork, but replay still performs observed pixel writes and actual title glyphs are drawn separately. #1423 makes colour resolution cheaper, not retained-pixel bookkeeping. #1597 removes whole redundant paint passes, so it also avoids their new text cost. #1598 and the separate local bulk-fill work target that remaining per-pixel cost directly.
We have not run a matched before/after-#1524 experiment for the older PRs. Their percentage changes therefore cannot yet be quantified; the old numbers must not be ranked against the 41%/31% post-overhaul results as if they shared a baseline. More rendering CPU can also dilute a JIT optimization's percentage without making the JIT optimization itself slower. The mechanism explains why priorities changed, but is not proof of a particular slowdown or speedup caused by #1524 alone.
Theme is another comparison boundary: the 41% and 31% post-overhaul replays use
classic-system7. #1421's non-classic theme-artwork cache is bypassed in that path, so it does not add its historical 20.8% saving to those runs. The old5f21681CLI default wassystemless-default; the current CLI default isclassic-system7. Changes in theme, scheduling and retained rendering must be separated before attributing a changed effect size specifically to #1524.Summary
Keep Sound Manager callback servicing at its existing boundaries, but leave native window/menu composition to the desktop frontend's presentation pass. In two matched, fixed-simulated-time SC2K city comparisons this reduced process CPU time by 40.1% and 42.0%. EV Override did not show an established benefit; its results and caveats are included below.
This is a standalone two-file change, now integrated with upstream
b5f4f083/ Systemless 0.39.1 ata7689d11. The performance measurements below were collected on the earlierb4bbe723/ 0.39.0 base and are retained with that provenance; they have not yet been repeated after upstream #1595. It does not include or require #1596, the earlier trap/CopyMask experiments, dependency changes, or benchmark instrumentation. The measurement builds used the same #1596 replay driver in both arms; that distinction matters and is explained below.Why this work was being done
The desktop frontend already divides execution into small foreground CPU batches, audio mixing/callback work, and an outer presentation pass.
run_gui_cpu_slicecorrectly defers frame completion. However,service_pending_sound_workcalledrun_pending_sound_work, whose completion path performed:The existing public callback API is also usable by standalone callers, so full completion is a reasonable contract there. It is unnecessary repaint work when the caller already owns presentation. A sound callback slice is not a video frame; multiple such slices may occur before presentation.
After the TrueType/retained-text work in #1524, repainting can erase and recreate glyph coverage, hash per-pixel metadata, and capture/restore that detail around clipped window drawing. A diagnostic SC2K baseline sample placed 3,333 of 3,541 main-thread samples under native chrome redraw, roughly split between internal slice completion and outer presentation. These are inclusive sampled stacks, not an exact call census or an Activity Monitor CPU reading.
The useful native-macOS-app analogy is to service audio promptly and coalesce UI work at presentation. This change avoids redundant host drawing; it does not lower the guest execution rate, skip guest callbacks, introduce a game-specific idle heuristic, drop audio, or reduce glyph resolution.
Implementation and correctness boundary
The private
finish_frame: boolbecomes an explicitFrameFinalizationpolicy:DeferredAudioOnlyCompleteAll old callers map to their previous
DeferredorCompletebehavior. The existing publicrun_pending_sound_workremains a complete slice. A newrun_gui_pending_sound_workselectsAudioOnly, and the desktop sound helper uses that API. Its documentation requires a caller that owns outer presentation. Other frontends are not silently switched.The pending-work/halt checks, callback instruction limit, and per-frame reserved sound budget remain unchanged. The classic normal completion path retains
sound_interrupt_dispatched || sound_work_only; it still loads ready double buffers, drains guest-written sound queues, synchronizes channel fields, and preserves callback dispatch boundaries. Zero requested mixed samples does not make those services optional.The policy is carried through UI-yield and native-engine handoff/early-return paths, not just the ordinary 68K tail. Existing PPC render/front-buffer/VFS synchronization is not accidentally disabled. ExitToShell paths that previously repainted without audio remain audio-free.
composite_framestill performs the native chrome work and its parked-idle write-journal validation. No raw-memory shortcut, unchecked framebuffer write, or disabled retained-text observer is introduced.A concrete intra-frame difference: guest code that overwrites a chrome pixel and reads it back after a sound callback, but before outer composition, can now see its own write instead of a callback-triggered chrome restore. That state was already possible between deferred foreground CPU slices; this change removes the additional callback restore point.
There is an important scope limit: the current chrome routine also blits window contents and maintains fullscreen/visibility state and overlays. Deferring it changes intra-frame timing, so it is not enough to argue that “decoration cannot affect the guest.” Tests and game checkpoints cover the cases below; they do not prove every possible transient guest framebuffer read or overlay interaction.
Actual CPU measurements
Intel macOS, Rust 1.96.1, published m68k 0.13.0 / ppc 0.6.0, default release optimization (fat LTO, one codegen unit). Both arms used identical dependencies, the corrected #1596 time-replay driver, and identical private phase counters. No compiler, sampler, or second emulator overlapped the production A/B runs.
Each workload ran one baseline→candidate pair and one candidate→baseline pair with fresh archive/save copies, the same input script, startup clock, 415,628-instructions/tick machine profile, and 60.15 Hz frontend clock. The acceptance metric is process user + system CPU time inside a fixed simulated-time phase, excluding launch and checkpoint PNG encoding. Host instructions and cycles are supporting measurements, not substitutes for CPU time. Fixed
--max-instructionstotals are not used to score these workloads.SC2K: new city, 600 frontend ticks
Phase 1,800→2,400: January→March 1900, an unobstructed new city, not a developed-city or budget-dialog benchmark. All four runs execute exactly 31,686,472 guest instructions within the phase.
Median paired CPU change: −41.033%. Before/after CPU-speed-limit snapshots were 100 throughout except baseline 2 ending at 97. Those snapshots cannot establish perfectly fixed clock/cache conditions, but the large actual CPU saving repeats in both orders. This is not a claim of 41% lower GUI Activity Monitor usage or a measured FPS improvement.
EV Override: flight, 3,000 frontend ticks
Phase 3,300→6,300: 202,572,307 guest instructions in every run.
No EV improvement is established, and a small regression is not excluded. The paired CPU median is +1.367%; neither pair is discarded. Speed-limit snapshots were 97→100 for baseline 1, 100→100 for candidate 1, and 97→97 for both pair-2 arms. The callback census below helps explain the near-zero instruction change. The small cycle increase in both pairs must not be dismissed as proven thermal noise; code-layout/cache effects also remain possible.
Why SC2K improves but EV flight does not
The saving depends on how often an extra completion repaint occurs × how much work that repaint does. It is not a general speedup of guest instructions or the sound mixer, and it is not selected by game name.
Separate diagnostic replays with the existing
SYSTEMLESS_TRACE_SOUNDflag counted callback events strictly between the same phase markers. Both replays retain the exact guest progress, final logical screenshot, and captured mono output of the untraced baseline. These runs overlapped compilation and printed tracing, so none of their CPU timings are used in the tables above.EV absolutely does use guest sound callbacks. Saying it does not would be wrong. SC2K's smaller buffers require replenishment approximately every 16.8 ms, versus about 46 ms for EV's stream, so SC2K offers more callback-boundary repaint opportunities per simulated second. These counts are callback firings/completions, not an instrumented count of
redraw_chromecalls: one callback can span more than one bounded CPU slice.The more important difference is the cost of the chrome pass:
(0, 0, 600, 800)procID=2window andMBarHeight=0; the flight checkpoint has no Macintosh menu/title bar.redraw_chromealready gates menu drawing on visible-menu/non-fullscreen state and gates window chrome on a positive menu-bar height/non-fullscreen state. In this state those expensive native menu/window painting paths are absent. Removing extra completion calls therefore eliminates little work, even though the sound callbacks themselves are frequent. The ship, planet, radar and instrument-panel rendering are game drawing, not the redundant native chrome targeted by this patch.This explains the contrast between about 42.14% fewer host instructions in SC2K and only 0.01% fewer in EV flight. It does not establish the cause of EV's small positive cycle/CPU deltas, nor does it imply EV's registration/dialog/menu workloads must behave like flight. It also does not predict uniformly low SC2K CPU during expensive game animations such as the spinning newspaper. Those are different workloads that still need targeted profiling.
Verification
September 8 upstream refresh: normal merge commit
a7689d11incorporates #1595's foreground dialog callback clock/preemption and nested-dialog fixes. The callback predicates introduced upstream are retained; the finalization policy does not replace them. Refreshed-head CI passes Linux build/tests, headless/package checks, licenses, and the nonblocking formatting/clippy job. The local test counts and CPU/GUI results below remain attributed to their explicitly named earlier heads; this CI run does not establish new CPU numbers or macOS visual acceptance.b301ba58003db172; EV: 2,293,347, hash96e0d7e286d6c874(whole replay captures).metal_present::tests::minification_retains_thin_strokes_between_sample_centers; the ignored test is the opt-in actual Metal capture. Neither is counted as a pass.6cc7e5f7, withci-test,--locked --offline --features test-support, four codegen units, one build job, and two test threads.6cc7e5f7: Linux build/library/desktop tests, classic 68K and PowerPC showcase assertions, headless build, documentation/package checks, dependency licenses, and the nonblocking formatting/clippy job. The known local macOS Metal exclusion above remains disclosed; green Linux CI does not erase that limitation or substitute for CI ona7689d11.The initial new glyph test exposed a fixture mistake: populating only the host menu cache did not install the canonical guest MenuList, so no title glyph was painted. The corrected fixture uses the actual traps and asserts visible detail before the comparison. Also, the fourth field of the RGB capture is a cumulative draw counter, not visible state; equal output with fewer draws is the intended result. These were test-only corrections, not removed correctness checks.
Alternatives considered and remaining work
finish_frame=false: rejected by source analysis, not a benchmark. That would also skip required sound queue/refill servicing and alter native synchronization.mix_gui_audio_slice(0)afterward: not equivalent as-is; it supplies a different callback-dispatch flag and can change the callback boundary.A separate post-change SC2K diagnostic sample found no chrome repaint under sound-slice completion, but 1,773 of 2,277 main-thread samples (77.87%) remain under outer chrome composition. This confirms the intended change in sampled stacks and leaves substantial performance work. Avoiding unnecessary menu-strip snapshot/restore or unchanged window/menu repaint is a separate next optimization, not a saving claimed by this PR. The sampled run is excluded from the CPU acceptance pairs. The user-observed spinning-newspaper CPU peak is also a specific follow-up workload: capture that transition under identical input and compare/profile baseline and candidate separately from steady-city performance.