Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
dc29765
[GPU] The command processor write history records only when its diagn…
xnetcat Oct 26, 2026
418f6bf
[GPU] Fan, quad, 8-bit and restart index expansion writes into per-th…
xnetcat Oct 26, 2026
e8dfe3d
[GPU] A register snapshot is handed out again while no register write…
xnetcat Oct 26, 2026
ae79690
[GPU] The command queue pulses its gate only when a thread waits and …
xnetcat Oct 26, 2026
cdf3242
[Split] The mirror translator counts cache hits and misses only under…
xnetcat Oct 26, 2026
2a9b016
[Split] A posted call wakes one sleeping server thread instead of all…
xnetcat Oct 26, 2026
c99e502
[Split] Managed-write interest is per 4 KiB page, and one watch chang…
xnetcat Oct 26, 2026
d30b0a5
[GPU] Per-draw bindings reuse the ring ranges, window placement and e…
xnetcat Oct 26, 2026
71c0abd
Merge perf/fix-w6: per-draw binding reuse
xnetcat Oct 26, 2026
2074263
Merge perf/fix-w10: split crossing costs
xnetcat Oct 26, 2026
983fbb4
[GPU] Draws through moving descriptor tables find their materializati…
xnetcat Oct 26, 2026
f78c21b
Merge perf/fix-w5: content-keyed resource materialization
xnetcat Oct 26, 2026
8601ce1
[GPU] A guest store into a large staged entry re-copies only its page…
xnetcat Oct 26, 2026
3cb33fe
Merge perf/fix-w8: page-granular staged cache and pooled single-copy …
xnetcat Oct 26, 2026
66ca26e
Merge perf/fix-w4b: command processor per-draw cuts
xnetcat Oct 26, 2026
6564ccd
[GPU] Translated shaders compile in relaxed math, test depth early wh…
xnetcat Oct 26, 2026
a63c92c
Merge perf/fix-w9: relaxed math, early depth, global record spans
xnetcat Oct 26, 2026
63ad96d
[GPU] Draw lookups read each guest table once, resolve each color tar…
xnetcat Oct 26, 2026
16d59cc
Merge perf/fix-w4a: pipeline lookup per-draw cuts
xnetcat Oct 26, 2026
15ae93f
[Kernel] Short sleeps and yields nap on an escalating schedule; nativ…
xnetcat Oct 26, 2026
cf24ac6
Merge perf/fix-w1: sleep nap ramp, GPU worker QoS, guest exit, thread…
xnetcat Oct 26, 2026
6ba3553
[Kernel] Drop the sceKernelGetThreadTime export: neither the SDK head…
xnetcat Oct 26, 2026
eac89b5
[Perf] Guest-to-HLE crossings: AGC builder leaf, native clock stubs, …
xnetcat Oct 26, 2026
32894af
Merge perf/fix-w2: builder leaves, clock stubs, crossing state
xnetcat Oct 26, 2026
183f7de
[Fiber] Replace the global fiber gate with per-fiber lock stripes
xnetcat Oct 26, 2026
c8b7a7c
[HLE] Resolve __tls_get_addr from a per-thread cache without the temp…
xnetcat Oct 26, 2026
9cb412f
[Ult] Wake only the waiters of the ULT mutex or queue that changed
xnetcat Oct 26, 2026
c1426a0
[Sched] Hand a woken guest thread straight to its runner and resume i…
xnetcat Oct 26, 2026
bba5d64
[Pthread] Spin for a mutex grant only while the owner is running, for…
xnetcat Oct 26, 2026
875c4fb
Merge perf/fix-w3: guest scheduler direct wake, shared resume stub, a…
xnetcat Oct 26, 2026
f7252cc
[Cpu] A missed sceKernelPollEventFlag is an expected result, like a m…
xnetcat Oct 26, 2026
0fce696
[GPU] Wait for the write fence after a GPU word's own submission, not…
xnetcat Oct 26, 2026
15bab68
Merge perf/fix-w7: write fences narrow host reads of GPU-written words
xnetcat Oct 26, 2026
75e9e62
[Tests] The GPU-word wait tests stop their watchdog before releasing …
xnetcat Oct 26, 2026
720a1db
[CLI] The guest's main thread runs on a plain managed thread again; S…
xnetcat Oct 26, 2026
e457cc4
[GPU] A shader identity memo whose memory cannot compare in place fal…
xnetcat Oct 26, 2026
cde04b7
Merge perf/fix-w4a-regress: shader identity memo falls back when memo…
xnetcat Oct 26, 2026
94ada05
[Agc] Wait for the frame boundary after releasing the submission gate
xnetcat Oct 26, 2026
6a660e8
[GPU] Fuse Metal texture alias transitions and discard dead contents
xnetcat Oct 23, 2026
18323b7
[GPU] Integrate audited runtime and materialization performance fixes
xnetcat Oct 26, 2026
379fa02
[Bench] Publish synthetic audit harness and change coverage
xnetcat Oct 26, 2026
a6b2978
[Tests] Allow the Metal performance audit benchmark friend
xnetcat Oct 26, 2026
3cf2d65
[GPU] Accelerate portable dword vertex fetches
xnetcat Oct 26, 2026
52f07c7
[GPU] Record matched Silent Hill shadow-pass runtime results
xnetcat Oct 26, 2026
35eb5de
[GPU] Add native occlusion and preserve measured Silent Hill performa…
xnetcat Oct 27, 2026
fb71707
[GPU] Enable validated performance paths by default
xnetcat Oct 27, 2026
eabcd9f
[GPU] Remove redundant mesh register copy allocations
xnetcat Oct 27, 2026
4281e48
[GPU] Record default-settings room controls and CPU bottlenecks
xnetcat Oct 27, 2026
b4cc829
[GPU] Reuse completion values once submitted work is finished
xnetcat Oct 27, 2026
37358ee
[Tests] Isolate mapped-copy fixture addresses and record runtime cont…
xnetcat Oct 27, 2026
f70ce37
[GPU] Add bounded local draw reuse with allocation and runtime controls
xnetcat Oct 27, 2026
5899682
test(perf): qualify default acceptance and fix polling timing
xnetcat Oct 27, 2026
c5ae56c
test(perf): correct Rosetta thread CPU timing with Mach counters
xnetcat Oct 27, 2026
4b6bad9
[GPU] Enable bounded vertex layout history with adaptive miss backoff
xnetcat Oct 27, 2026
500a1c1
docs(perf): record default-on runtime and occlusion drain evidence
xnetcat Oct 27, 2026
f2bd8c7
[GPU] Resolve short prefetch queues on the caller by default
xnetcat Oct 27, 2026
d7d1f48
[GPU] Cache pixel-input instruction analysis per decoded program
xnetcat Oct 28, 2026
7558bab
[GPU] Apply the pipeline look-ahead time limit only after its first b…
xnetcat Oct 28, 2026
cdb27f4
[GPU] Share one wave64 exchange between back-to-back constant-lane V_…
xnetcat Oct 28, 2026
c07ecdd
[Loader] Let the spin-nap pass patch pause loops behind a hoisted jum…
xnetcat Oct 28, 2026
89f89c5
[Loader] Absorb jump padding into a spin-nap patch only when the func…
xnetcat Oct 28, 2026
9aba2fe
[Audio] A guest thread that just read a full AudioOut2 queue waits fo…
xnetcat Oct 28, 2026
4be8a89
[GPU] Land a held batch of label copies through one compute dispatch …
xnetcat Oct 28, 2026
2030869
[Tools] The perf-audit suite measures an sceAgc builder in the split …
xnetcat Oct 28, 2026
3cc7875
[HLE] Write an AGC builder's cursor word and packet as one fused gues…
xnetcat Oct 28, 2026
33d37c4
[CPU] Answer fixed-shape AGC packet builders with a native stub in th…
xnetcat Oct 28, 2026
b50c83c
[CPU] Stub sceAgcCbSetShRegisterRangeDirect and sceAgcCbSetShRegister…
xnetcat Oct 28, 2026
880bb8b
[GPU] Build a render encoder's debug label only when encoder labels a…
xnetcat Oct 28, 2026
5778913
[GPU] A prefetched draw that is already resolved takes no clock reads.
xnetcat Oct 28, 2026
14aaf81
[GPU] Repeated storage-binding and command-read lookups skip the walk…
xnetcat Oct 28, 2026
7dd4296
[GPU] An occlusion dump resolves, invalidates and notes its blocks as…
xnetcat Oct 28, 2026
e4b0017
[GPU] Add env-gated benches for interpreter throughput, end-of-pipe a…
xnetcat Oct 28, 2026
ac594d1
[GPU] A register table zeroes a stack block sized to its entries, and…
xnetcat Oct 28, 2026
29f064c
[GPU] The worker stream bench adds a five-dump, a WRITE_DATA and a ze…
xnetcat Oct 28, 2026
1f56f55
[GPU] An env-gated bench records repeated Metal draws and reports mic…
xnetcat Oct 28, 2026
7d2dc99
[GPU] A pipeline key compares only the vertex bindings and attributes…
xnetcat Oct 28, 2026
d5314cf
[GPU] The draw record bench can sample larger linear images.
xnetcat Oct 28, 2026
550395c
[GPU] The draw record bench pre-fills a title's state sizes and recor…
xnetcat Oct 28, 2026
438d7e4
[GPU] A recorded GPU write forgets pending overlay bytes without allo…
xnetcat Oct 28, 2026
bddded9
[GPU] Recorded DCC fills are found by the page they start on instead …
xnetcat Oct 28, 2026
0131fb3
[GPU] A window first touched mid-submission is appended to the reside…
xnetcat Oct 28, 2026
7627a3c
[GPU] An env-gated bench times translator code shapes against cheaper…
xnetcat Oct 28, 2026
e2a8d47
[Shader] Raw buffer dword loads read at a clamped index and select ze…
xnetcat Oct 28, 2026
a488f74
[Tools] The shader compile bench times every cached shader on the GPU…
xnetcat Oct 28, 2026
a6b43e6
[GPU] Held label copies land through the scatter dispatch across up t…
xnetcat Oct 28, 2026
df118f2
[GPU] An occlusion dump queues behind the deferred draws instead of d…
xnetcat Oct 28, 2026
5b0f09c
[GPU] An occlusion batch reads its counts buffer's contents pointer o…
xnetcat Oct 28, 2026
cbf0564
[GPU] Draws, CP writes and occlusion dumps allocate less on the worker.
xnetcat Oct 29, 2026
a6b92b4
[GPU] A depth target's stencil reaches the R8 image over it on the de…
xnetcat Oct 29, 2026
c6679c6
[GPU] Name the GPU-written images a guest store waits for, behind a t…
xnetcat Oct 29, 2026
2de518e
[GPU] The Metal command stream runs on its own thread by default.
xnetcat Oct 29, 2026
7204d8f
[GPU] Barriers and queued writes no longer hold back the program reso…
xnetcat Oct 29, 2026
d2f2e02
[GPU] A wait on a word a queued fill writes reads the fill's word ins…
xnetcat Oct 29, 2026
1dc741d
[Docs] List the command-thread, stencil, ungated and queued-fill defa…
xnetcat Oct 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
37 changes: 27 additions & 10 deletions docs/macos/split-protocol.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Two-process split: wiring contract (Phase 2)

Status: implemented behind the flag, 2026-10-24 (SharpEmu.GpuSplit; transport protocol 2). Design: `SPLIT-PLAN.md`
Status: implemented behind the flag, 2026-10-24 (SharpEmu.GpuSplit; transport protocol 7). Design: `SPLIT-PLAN.md`
(repo-external draft), Phase 0 rates: commit `fc963f02`, shared memory: `docs/macos/split-shared-memory.md`,
transport: `src/SharpEmu.GpuRemote`.

Expand Down Expand Up @@ -31,7 +31,11 @@ page watches 100-2600/s (batch); submissions 30 and 200-350/s; SubmitDone/flips

All records are little-endian blittable structs (`GpuSplitMessages.cs`) on the transport rings; synchronous calls use
`SyncCallTable` both ways: A → B (`SyncCalls`, signal-safe on the A side) and B → A (`ReverseSyncCalls`, ordinary B
threads). Add new kinds at the end of the enums; bump `GpuRemoteProtocol.Version` (now 2).
threads). Add new kinds at the end of the enums; bump `GpuRemoteProtocol.Version` (now 7).

Revision 7 combines frame-boundary messages, input-change/display reservations and
the pending-writeback page summary. Independent revisions 4, 5 and 6 are rejected
on attach because their shared layouts differ. Both processes must use revision 7.

### Start

Expand All @@ -56,6 +60,7 @@ fused program.
| `HostFrame` | width, height, total, offset + piece | `Submit` of a CPU frame (splash, video), in pieces of 256 KiB written back to back. |
| `GamepadOutput` | rumble, trigger rumble, adaptive triggers, lightbar | To the pad, which lives with B's window. |
| `VideoOptions` | window options | Options configured after the start. |
| `FrameBoundary` | ticket | The ordering half of a split `SubmitDone` (`BeginSubmitDone`): B queues the boundary behind the submissions before it and keeps its wait for `WaitFrameBoundary`. A sends it under its submission gate and waits after releasing it. |

### A -> B, synchronous (`SyncCalls`)

Expand All @@ -65,10 +70,11 @@ many, so they come after A's earlier records.
| Opcode | Request | Response | Semantics |
|---|---|---|---|
| `SubmitDone` (barrier) | - | IdleOutcome | B's `SubmitDone`: returns at the frame boundary. With A's session shutdown requested A sends `RequestClose` and answers `Cancelled` itself. |
| `WaitFrameBoundary` (barrier) | ticket | IdleOutcome | The waiting half of a split `SubmitDone`: runs the wait B kept for the `FrameBoundary` record of that ticket. |
| `BracketBegin` | kind, barrier (AfterPendingCommandStreams only) | bracket id | `GuestGpuMemory.RunMappingChange` / `Unregister` in A (kind MappingChange: the relay's `TryRunAfterPendingWork`) and `RunAfterPendingCommandStreams` (kind AfterPendingCommandStreams). B parks its GPU worker (`WorkerBracket`: B's own `RunMappingChange` / `RunAfterPendingCommandStreams` hand the worker a park loop); A then runs the work itself, as the GPU queue thread (`RemoteGpuQueueRelay.IsGpuQueueThread`). |
| `BracketEnd` | bracket id, viewLogSequence | - | B syncs its replica that far on the parked worker and lets it go. |
| `Fault` | kind, address, size | handled, restoration version | From A's signal handler (`RemoteImageStore.MarkCpuWrite`, allocation-free). B runs its stores' fault path, then waits until A applied every protection B requested so far: the guest retries the access as soon as this answers. Every write fault crosses (the page faulted because B protected it). When no store handled it, B answers its guard's write-restoration version of the page: a watch removed before the fault reached the store. A retries the store once per version when the page is writable (one process's `TryRetryReleasedWrite`, from B's guard). |
| `CpuWrite` | address, size | handled | A managed write before it lands, when the summary marks it: B's `MarkCpuWrite` (the cache publishes a GPU-written image before the write lands over it). From A's reverse-call threads and from inside a bracket it is sent as a `CpuWrite` record instead (there B's worker cannot take the writeback: the in-process cache's rule for a store from the worker). No barrier. |
| `CpuWrite` | address, size | handled | A managed write before it lands, when both the interest and pending-writeback summaries mark it (all interested pages count as pending until B publishes the pending summary live): B's `MarkCpuWrite` (the cache publishes a GPU-written image before the write lands over it). From A's reverse-call threads and from inside a bracket it is sent as a `CpuWrite` record instead (there B's worker cannot take the writeback: the in-process cache's rule for a store from the worker). No barrier. |
| `SpanRegister` / `SpanProtect` / `SpanUnregister` | address, size, protection, bracket id | (handled) | B's `GuestGpuMemory` follows A's spans and permission ledger (`IGuestSpanObserver`, `RemoteImageStore.Unregister`). B's page guard owns the host protections of every span: A's `Register` / `NoteProtected` apply none of their own when the call succeeds (A's watch-less guard would hand back, for a moment, rights B's watches took); on a failed call A applies the plain guest protection. An unregistration inside a bracket runs on B's parked worker. B waits for the protections it requested before it answers, so they land before A unmaps, remaps or lets the guest go on. |
| `Query` | which | value | Performance counters, HDR output support, mesh pipelines. |

Expand Down Expand Up @@ -130,9 +136,13 @@ bridge refuses (and reports) a call from the ring thread.
- **Protection acknowledgement** (offset 0, a `SharedCounter`): A raises it to a `ProtectRequest`'s sequence once that
request and every one before it is applied; B waits on it. (The first contract had a `ProtectAck` ring record; a
shared counter means B's waits never depend on A's ring writers, which may be blocked on a full ring.)
- **GPU-interest summary** (offset 4096, 2 MiB): one bit per 64 KiB below 1 TiB, set while any page in it carries a
`PageGuard` watch in B (`IPageWatchObserver`, under the guard's block lock; one word per 4 MiB block). B sets a bit
before the watch's protection is requested, with a full barrier; A filters managed writes with it.
- **GPU-interest summary** (offset 4096, 32 MiB of address space, touched only where watches are): one bit per 4 KiB
page below 1 TiB, set while the page carries a `PageGuard` watch in B (`IPageWatchObserver`, under the guard's block
lock; 16 words per 4 MiB block). B sets a bit before the watch's protection is requested, with a full barrier; A
filters managed writes with it, so a write next to a watched page (command buffers sit beside the images they draw
to) does not block. `SHARPEMU_SPLIT_STATS=1` adds `ManagedWriteInterest` (writes that hit a set bit),
`ManagedWriteCall` (the blocking `CpuWrite` calls, with their average time) and `ProtectWait` (confirmation waits,
average time) to the `[SPLIT-X]` line.

## Teardown and failure

Expand All @@ -148,15 +158,22 @@ bridge refuses (and reports) a call from the ring thread.
- B's console is A's console (inherited); B tees it into `SHARPEMU_GPU_HOST_LOG`, which the CLI sets to
`<log>.gpuhost.log` when it runs with `--log-file`.

## Watch protections wait once

A watch change (`AddWatch`, `AddWatchMask` and their removals) sends every protection it needs and waits for A's
acknowledgement once, after the page guard released its block locks (`IProtectionBatchSpace`; the shared counter is
cumulative, so the last request's number covers the rest). A change that found its pages already watched by another
thread, whose protection that thread has not confirmed yet, waits for that thread's number too. Protections made
outside a watch change (ledger changes, `TryProtect`) still wait where they are made. `SHARPEMU_SPLIT_BATCH_PROTECT=0`
waits at each protection, as before.

## Not carried yet

- Host movies (`MoviePresentationServices`: frames A's decoder hands the presenter) do not reach B's presenter.
- `TraceGpuFlip` (diagnostics) does nothing in B.
- Private guest memory (above).
- Fast paths: restricting watches wait one round trip each (batch them and wait once before the bytes are read);
`CanPresentFlip`/`NextFlipPresentTimestamp` cost a round trip per presenter pass (mirror the pacing state); managed
- Fast paths: `CanPresentFlip`/`NextFlipPresentTimestamp` cost a round trip per presenter pass (mirror the pacing state); managed
writes under a watch are a call each.
- Known windows, not closed yet: B's page-guard block lock (a spin) is held while a protection that takes rights away
waits for A's acknowledgement; managed writes made inside a bracket reach B as records and may be handled after the
- Known windows, not closed yet: managed writes made inside a bracket reach B as records and may be handled after the
worker resumes; the shutdown no longer orders B's watch teardown before A releases its page guard (A's guard holds no
watches, and A acknowledges late requests without applying them).
113 changes: 113 additions & 0 deletions docs/performance/benchmark-index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<!-- Copyright (C) 2026 SharpEmu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later -->

# Audit changes and benchmark index

The combined PR contains the complete round-one integration ending at
`cde04b713e60de7ab02dfa092e8f91d086df0c83` (37 commits above the audit's main
baseline), the two round-two committed changes and the accepted round-two WIP.
The original combined implementation is `18323b71870897ea657939d621a1bacfbde83a2b`.
The later benchmark packaging adds reusable source and documentation; it does not
turn historical component measurements into measurements of the combined build.

## Implementation coverage

| Area | Changes included |
|---|---|
| CPU and HLE crossings | AGC builder leaf path, native clock stubs, host-thread pthread identity, shared thread state, in-place large memset, expected-result logging and disabled-trace formatting guards |
| Guest scheduling | Direct wake handoff/shared resume stub, owner-aware bounded mutex spinning, per-fiber lock stripes, targeted ULT wakes, TLS caching, sleep/yield backoff and thread/exit behavior |
| Draw preparation | Pipeline/shader identity memoization with fresh-memory fallback, table/color-target lookup reuse, scratch index expansion, unchanged register snapshot reuse and diagnostic-only write history |
| Resource materialization | Content-keyed reuse across moving descriptors, pointer-key costs, linear merge of recorded reads and bounded cold-scan retention; descriptor, mapping, bounds and snapshot invalidation tests |
| Metal storage/binding | Page-granular staged refresh, reusable retile staging, binding/residency reuse, GPU-pristine snapshots, owned texture-view cache and alias/narrow-consumer transitions |
| Synchronization | Exact GPU-written word fences, host completion publication through callbacks at the same serial, tracked store/barrier handling and waiter registration/lost-wake coverage |
| Split transport | One protocol-7 layout, frame-boundary waits outside the submission gate, event-driven input, one-use display reservations, pending-writeback page summary and cross-architecture smoke coverage |
| Shader translation | Relaxed-math/early-depth handling, global-record reuse, grouped raw loads, native min/max and reciprocal lowering; precise sqrt retained; contraction remains opt-in |
| Audio and host overhead | AJM disabled-formatting path, environment lookup and audio context work, helper/compiler thread policy and archive/prewarm handling |
| Regression fixes | Six Libs fixture/API failures, scattered-view alignment, lossy allocation footprint, enumerated native leak counters and ordered fake-store writes |

The [K1–K21 disposition table](combined-audit.md#workstream-dispositions) records
what was accepted, rejected or left opt-in. Concurrent tiling is not the default;
GC/tiering changes, unsupported lock-free/parallel planning, extra barrier/store
elision, decoder pooling and ABI changes were not accepted.

A source inventory check found 124 distinct recovered changed paths: 122 are
tracked in the combined PR. The two other paths are the temporary input harness
(replaced by `InputDeliveryBenchmarks.cs`) and `SpinNapElfSurvey.cs` (an ELF survey,
not an accepted emulator fix). K1's survey-only loader field was also omitted.
This is an accounting of accepted implementation, not a claim that every discarded
experiment or local artifact belongs in the PR. Original synthetic eboot binaries,
commercial captures, raw logs, build outputs and machine diagnostics stay outside it.

## Runnable benchmark sources

| Benchmark | Source / instructions |
|---|---|
| CPU, queue, memory, translator, pipeline and synthetic Metal paths | [PerfAudit harness](../../tools/SharpEmu.Tools.PerfAudit/README.md) |
| Final cold/stale/hit/ring comparison | [Materialization harness](../../tools/SharpEmu.Tools.MaterializationBench/README.md) |
| Split input latency | [InputDeliveryBenchmarks](../../tests/SharpEmu.GpuSplit.Tests/InputDeliveryBenchmarks.cs), `SHARPEMU_INPUT_BENCH=1` |
| Grouped loads, min/max, reciprocals, contraction and hints | [MslTranslatorOverheadBenchTests](../../tests/SharpEmu.ShaderCompiler.Metal.Tests/MslTranslatorOverheadBenchTests.cs) |
| Relaxed math, global records and early depth | [MslGpuShaderTimeBenchTests](../../tests/SharpEmu.ShaderCompiler.Metal.Tests/MslGpuShaderTimeBenchTests.cs) |
| Texture transfer and alias CPU/GPU costs | [MetalImageTransferTests.Timing](../../tests/SharpEmu.Gpu.Metal.Tests/MetalImageTransferTests.Timing.cs) |
| Narrow consumer transfer work and exact bytes | [ConsumerRunSceneTests](../../tests/SharpEmu.Gpu.Metal.Tests/Scenes/ConsumerRunSceneTests.cs) |

Run the shader benchmarks with `SHARPEMU_GPU_BENCH=1 SHARPEMU_TEST_REQUIRE_DEVICE=1`,
Release configuration, a class filter and `shader-serial.runsettings`. For example:

```sh
SHARPEMU_GPU_BENCH=1 SHARPEMU_TEST_REQUIRE_DEVICE=1 ~/.dotnet-x64/dotnet test tests/SharpEmu.ShaderCompiler.Metal.Tests -c Release -m:2 --settings docs/performance/shader-serial.runsettings --filter FullyQualifiedName~MslTranslatorOverheadBenchTests --logger 'console;verbosity=detailed'
SHARPEMU_INPUT_BENCH=1 ~/.dotnet-x64/dotnet test tests/SharpEmu.GpuSplit.Tests -c Release -m:2 --filter FullyQualifiedName~InputDeliveryBenchmarks --logger 'console;verbosity=detailed'
```

## Measurements and limits

These are different experiments with different baselines; they must not be added
together or converted into a game-FPS estimate. Final admission figures are from
alternating runs of the combined build. Other figures are historical isolated
component comparisons retained from the recovery audit.

| Experiment | Before | After | Interpretation |
|---|---:|---:|---|
| Final cold allocation, tiering disabled | 8,264 B/op | 6,379.9–6,382.6 B/op | About 23% less allocation; timing remains inconclusive |
| Final cold allocation, tiered/PGO | 8,160 B/op | 6,378.9–6,384.2 B/op | About 22% less allocation; host controls drifted |
| Final stale descriptor allocation, tiering disabled | 8,392.2–8,392.3 B/op | 7,032.6–7,035.4 B/op | About 16% less allocation |
| K17 strided translator, x64 tiering-disabled ABBA | 29.9 / 25.9 ns; 47 B/op | 4.9 / 5.3 ns; 0 B/op | Historical component result |
| K17 fragmented translator, same experiment | 153.1 / 155.8 ns; 45.2 B/op | 20.8 / 22.2 ns; 0 B/op | Single-view case instead slowed slightly; ~40 KiB inline storage per translator |
| K4 input delivery p50 | 3.48 / 3.11 ms | 0.10 / 0.10 ms | Two transport halves in one process; not guest draw latency |
| K12 narrow linear consumer retile | 16,588,800 bytes | 15,360 bytes | Deferred work; later full consumption still does the remaining work |
| K12 narrow tiled consumer retile | 16,711,680 bytes | 983,040 bytes | Exact-byte correctness checked; not a timing result |
| K21 grouped-load GPU kernel | 9.173 ms | 4.972 ms | Best of three warmed synthetic GPU measurements |
| K21 min/max GPU kernel | 13.700 ms | 8.215 ms | Same narrow methodology |
| K21 reciprocal/rsqrt GPU kernel | 8.628 ms | 6.303 ms | Precise sqrt in both arms; not a whole-shader result |

[The full report](combined-audit.md#measurements-and-validation) retains both
medians and p95 ranges for every final A/B arm, historical main/cde cold controls,
allocation results, source identities, test counts and reproducible commands.
The linear merge alone did not prove a timing improvement. Concurrent tiling
regressed representative writebacks and remains opt-in. Compiler QoS and shader
hints have no established general speedup. Two admission-disabled parallel test-host
stalls remain unresolved; the full serial run passes unchanged assertions.

The original combined validation totals are **16,741 passed, 0 failed, 13 existing
skips**, plus REUSE, script checks and successful macOS CI at `18323b718`.
No commercial game was run for that original integration. Benchmark smoke checks
for this packaging change are reported separately from those full-suite results.

## Benchmark packaging validation

On x64 .NET 10.0.3/macOS, the standalone project builds with zero errors.
The documented short runtime invocation completes **75 benchmark rows** across
10 selectors. These short-run numbers are smoke evidence, not new A/B claims.
The generated Metal triangle completes and asserts the exact `0xFF0000FF` pixel;
a mismatch exits with an error. REUSE passes for all 2,816 files and whitespace
checks pass. The emulator algorithms are unchanged by this packaging follow-up;
the Metal project only grants the uniquely named tool access to its internals.
Full-suite/CI counts above belong to the original combined implementation.

## Portable vertex-fetch follow-up

See [shadow mesh fetch measurements](shadow-mesh-fetch.md) for the subsequent runtime
localization, generic formatted-load optimization, descriptor/bounds tests, synthetic
GPU benchmark commands and fallback costs. The subsequent matched Silent Hill
rooftop/title comparison observes 2.49 → 3.98 median guest FPS and 56.509 → 26.753 ms
mean shadow encoders. Its conditions, failed captures and remaining bottlenecks are
listed in that report; this is not a full-game gameplay benchmark.
Loading
Loading