Perf: dedicated strip-parallel composite (4 threads, BelowNormal) — UNIT 5
A composite splits into four horizontal row strips across four DEDICATED background threads; the calling thread takes a strip too (so four workers never mean five-way oversubscription), and anything under 64 rows runs inline. BlitContentRaw, BlitBakedElement and BlitOverlay now fan out. Output is byte-identical — strips already partitioned by row. The demotion deliberately does NOT come from Parallel.For. The pool is shared, so Thread.CurrentThread.Priority = BelowNormal inside it leaks into every unrelated work item that thread later runs — a full test run after trying it broke AudioPipelineTests.Mix_HonorsProviderGains_AndGameMute_ KillsTheLoopback. Established guidance agrees: use your own producer/consumer queue with your own Threads and set their priority yourself (https://stackoverflow.com/questions/524483/net-backgroundworker-objects-thread-priority, https://stackoverflow.com/questions/1578599/c-sharp-lower-thread-priority-in-thread-pool). Helpers/StripPool.cs is that queue. Four threads is the measured sweet spot: fewer leaves render over budget, more steals cycles from the game and the encoder and made render worse. The scaled blit's column LUT moves from a shared static buffer to ArrayPool<float>.Shared with a finally — a single static buffer is a data race the moment strips run in parallel. Gate: SceneCompositorTests.StripParallel_RepeatedRenders_AreByteIdentical — 8 renders of a scaled round webcam + mirrored image + master-sized flash over a short backdrop must be byte-for-byte identical, and must differ from a backdrop-only render so it cannot pass vacuously. It caught a loop-variable capture (new Thread(() => Worker(i)) saw i == Workers) that crashed the test host on the first run.
This commit is contained in:
+53
-7
@@ -10,6 +10,40 @@ Unpushed: `5ad0106` broadcast diagnostic → `74da99c` + `0fb2f9c` privacy/Test
|
||||
`8fbc370` diagnostic parse fix → `07a088a` **the explicit-bind fix** → `509ef36` window placement →
|
||||
`b2f2197` handoff → `UNIT 0` frame-cadence measurement.
|
||||
|
||||
## 🚧 IN FLIGHT 2026-10-05 — UNITS 4+5 landed: the render-budget work (needs a creator measurement)
|
||||
|
||||
**The 141ms → 33ms mystery is SOLVED, and it was geometry, not the build.** The opaque-backdrop 1:1
|
||||
row walk only engages when the source rect is **exactly** 1920×1080. On a 21:9 monitor the source
|
||||
rect shrinks to 1920×803 ("fit", not "fill"), so every render paid a ~2M-byte opaque-black prefill
|
||||
plus a ~1.54M-pixel per-pixel alpha-branch resample instead of one `BlockCopy`. 2560×1440 fits to
|
||||
1920×1080 exactly and takes the fast path. **Debug-vs-Release was NOT the cause** — today's Debug
|
||||
build with the game running on 16:9 measured 33ms average. Every recorded A/B matches.
|
||||
|
||||
**Shipped (all pixel-identical, all gated):**
|
||||
- **Unit 4** — opaque 1:1 fast paths widened to the whole-canvas case; **prefill only the uncovered
|
||||
letterbox rows**; a **per-column LUT** for the scaled blit (source column + 8.8 weights computed
|
||||
once per column, not a divide per pixel). Commits `0464593`, `08290ec`, `77d5120`, `dc13438`.
|
||||
- **Unit 5** — **`Helpers/StripPool.cs`**: one composite splits into four horizontal row strips
|
||||
across four DEDICATED `BelowNormal` background threads (the caller takes a strip too; inline below
|
||||
64 rows). ⛔ **Do not "simplify" this back to `Parallel.For` with a thread-priority poke** — the
|
||||
pool is shared, the demotion leaks into unrelated work, and it broke an audio test for real. Two
|
||||
sources + the failure are recorded in `MyMistakes.md`; the design note is in `ai.md` →
|
||||
"Render budget". Commits `623d77e`, `3901cb8`, + the strip-pool commit.
|
||||
- Gate test `SceneCompositorTests.StripParallel_RepeatedRenders_AreByteIdentical` — 8 renders of a
|
||||
multi-layer scene must be byte-for-byte identical, and must differ from a backdrop-only render so
|
||||
it cannot pass vacuously. It caught a loop-variable capture that crashed the test host on run 1.
|
||||
|
||||
**⏭ NEXT STEP — one Release recording, 60s, Live scene, Division 2 running.** This is the
|
||||
measurement that decides whether Unit 5 was necessary. Read the `FramePump stats` line (it now
|
||||
carries `social`/`ticker`/`flash`/`acq` per-stage timings and the pump-start line names the scene +
|
||||
compositor rect):
|
||||
- **avg render ≤12ms** → Units 4+5 did it; go straight to **Unit 6** (encoder backpressure:
|
||||
`worst submit` has hit 53ms with ~357 cumulative drops — real frame loss on top of the padding).
|
||||
- **still >16.67ms** → the remaining cost is NOT the blit loops; read the per-stage timings before
|
||||
touching anything else (do not guess — that is exactly the UNIT 0 lesson).
|
||||
- **Under ~30ms on 16:9 but 140ms on 21:9** → geometry is still costing us; the fix is the tier
|
||||
source-rect, not the compositor.
|
||||
|
||||
## 🚨 ANSWERED 2026-10-05 — the judder is the COMPOSITOR, not the camera. Unit 1 is the wrong next move.
|
||||
|
||||
Take `ty-20261005-1201-556-0001.mp4` (75.3s, 1920×1080, 60fps) + `startup.log`, read together:
|
||||
@@ -417,7 +451,7 @@ Detail and the open 11.12 re-check: [`TASKS/task-50-publish-recording.md`](TASKS
|
||||
## Test state
|
||||
|
||||
**402/402, 0 warnings, verified 2026-10-05** (395 + 6 `FrameCadenceTests` + 1 pump-stats integration
|
||||
test). Build the **test** project, not just the app project, or VSTest runs a stale DLL.
|
||||
test + 1 strip-parallel gate). Re-verified after UNITS 4+5. Build the **test** project, not just the app project, or VSTest runs a stale DLL.
|
||||
**Launch exe for the creator's test run: `bin/Debug/net8.0-windows10.0.19041.0/ytLive.exe`.**
|
||||
|
||||
**Confirmed environmental flake, NOT a regression (re-checked 2026-10-05, and it now fails
|
||||
@@ -427,6 +461,15 @@ fails identically on a stashed clean tree**, so it has nothing to do with UNIT 0
|
||||
interactive desktop session this shell does not have (the mouse drag never lands). Do not chase it;
|
||||
it is listed under Landmines as the known interactive-session test.
|
||||
|
||||
**⚠️ A game-playing session breaks the audio tests — know this before debugging it.** On
|
||||
2026-10-05 `AudioPipelineTests.Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback` failed with
|
||||
480/480 samples at `0.4` where `0` was expected, then passed in isolation minutes later. Cause: the
|
||||
user was in a **Division 2 boss fight**, and that test captures **audio loopback** — the game's own
|
||||
output landed in it. **An audio test failing during a gaming session is an environment fact, not a
|
||||
regression.** Same bucket as the interactive-desktop flake above: re-run in a quiet session before
|
||||
believing either. (It also briefly failed while I was probing `Parallel.For` thread priorities,
|
||||
which sent me after the wrong culprit entirely — the game was the simpler explanation.)
|
||||
|
||||
**⚠️ New flake, do not chase it as a regression:** on the *first* full-suite run,
|
||||
`AudioPipelineTests.MasterLimiter_CapsTheLiveMix_OnThePipe` failed with `Expected 0.891,
|
||||
Actual 0` — a **zero**, not a slightly-off value, which is the signature of the pipe not being
|
||||
@@ -449,31 +492,34 @@ whole-suite scheduling artifact of the same family as the `RealAppHost` STA land
|
||||
1. **~PUSH, then the creator's 60-second Test** — see the FIXED box above. Packaging is NOT next;
|
||||
the creator's ruling 2026-10-04: still in active development, so ship-time work waits and queued
|
||||
features come first.
|
||||
2. **Webcam cadence → Unit 1 (exclusive lock + 720p).** UNIT 0 is shipped and the counters are in
|
||||
2. **⏭ Read the Unit 4+5 Release recording (see the IN FLIGHT box at the top).** Then Unit 6
|
||||
(encoder submit backpressure: `worst submit` 53ms, ~357 cumulative drops) if render is inside
|
||||
budget; if render is still over, the per-stage timings name the stage — do not guess.
|
||||
3. **Webcam cadence → Unit 1 (exclusive lock + 720p).** UNIT 0 is shipped and the counters are in
|
||||
`startup.log`; the reading needs one 60-second creator recording (details + the exact
|
||||
read-the-log-together table in the IN FLIGHT box above). Then Unit 1: exclusive camera lock +
|
||||
720p, and Unit 2: the blinking SD badge. **Creator to-do, not yet queued: long-run
|
||||
resource/consumption/recycling benchmarking** (how long the app stays healthy under full burden) —
|
||||
record only, do not start it.
|
||||
2. **The packaging project + `Package.appxmanifest`** (TASK 48 items 2–3) — the next code unit.
|
||||
4. **The packaging project + `Package.appxmanifest`** (TASK 48 items 2–3) — the next code unit.
|
||||
Full trust, `webcam`/`microphone`, English only; Velopack deleted. Add the
|
||||
`DefaultRecordFolder()` comment in the same unit, and the WebView2 framework dependency +
|
||||
graceful-failure path.
|
||||
3. **TASK 50 — one-button "Publish this video"** (`TASKS/task-50-publish-recording.md`). Queued
|
||||
5. **TASK 50 — one-button "Publish this video"** (`TASKS/task-50-publish-recording.md`). Queued
|
||||
behind the Store submission by its own precedence note ("MSIX first, publish second — unless
|
||||
the creator reverses that"). Scoping is done; the build is not. ⛔ Do **not** flip
|
||||
`enableAutomaticArchiving` until it ships. It also carries an **open 11.12 re-check**: a
|
||||
publish feature makes the app *push* creator content to a third party, so the IARC answers
|
||||
must not go to certification describing an app that cannot publish.
|
||||
4. **Declare the IAP products in Partner Center** — the price is **settled** ($10/mo, $400
|
||||
6. **Declare the IAP products in Partner Center** — the price is **settled** ($10/mo, $400
|
||||
lifetime, no annual, 15% verified), so this is unblocked. **Blocks:** the Store listing and the
|
||||
`OverlayHost.xaml` copy. ⛔ The Individual-vs-Company account question is still unanswered —
|
||||
get it right before enrolling.
|
||||
5. **Polar teardown** (TASK 48 item 0) — `PolarLicenseService`, `PolarLicense`,
|
||||
7. **Polar teardown** (TASK 48 item 0) — `PolarLicenseService`, `PolarLicense`,
|
||||
`MainViewModel.License.cs`, the `OverlayHost.xaml` string, the `csproj`/`App.xaml.cs`
|
||||
Velopack sites. `IsPremium` ← Store entitlement. **Do this as one unit** — a half-torn-down
|
||||
licensing path is worse than either end state.
|
||||
6. **Vertical recording verification** — the compositor tier is proven by
|
||||
8. **Vertical recording verification** — the compositor tier is proven by
|
||||
`Render_VerticalTier_Outputs_1080x1920_From_The_Center_Crop`; the *record* path has never
|
||||
been run. `FramePump.cs:645` flags off-size tiers as a known follow-up.
|
||||
7. **WACK + certification notes + the privacy policy** (TASK 48 items 4–5) — the policy must
|
||||
|
||||
Reference in New Issue
Block a user