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.
2.4 KiB
2.4 KiB
Helpers — index
Cross-cutting utilities. See schema.md for the memory-map conventions.
| File | Purpose |
|---|---|
ViewModelBase.cs |
INotifyPropertyChanged + SetProperty<T>() — base for all ViewModels |
RelayCommand.cs |
ICommand implementation for all button actions |
AppLog.cs |
File logger to %APPDATA%\ytLlive\startup.log; startup checkpoints + unhandled-exception capture (the crash this map is named for) |
FocusPreservingListBox.cs |
ListBox subclass that keeps selection/focus coherent when the selected item is deleted; IsSelectable predicate skips hidden scenes; drag-reorder guard |
OAuthCredentials.cs |
Baked-in Google OAuth client ID/secret (desktop app; loopback callback) |
TokenStore.cs |
DPAPI-protected OAuth session persistence (%APPDATA%\ytLlive\ytLlive.auth, CurrentUser scope); Save/Load/Clear — sign-in survives restarts |
ImageCache.cs |
Image byte caching (assets live in the DB) |
InverseBoolToVisibilityConverter.cs / NotNullToVisibilityConverter.cs |
XAML value converters for visibility bindings |
AllTrueToVisibilityConverter.cs |
Multi-value AND-gate → Visible; gates the layer-row capture menu items on row-IsBackground + staged-scene-is-Live (TASK 25) |
CameraConflictProbe.cs |
Best-effort diagnostic: when a camera won't start, enumerates running processes known to hold cameras (OBS, Zoom, Teams, NVIDIA Broadcast, browsers, etc.) — Windows doesn't expose "which process has this device" via any public API, so this is a suspect list, not a verdict |
StripPool.cs |
Four DEDICATED background threads at BelowNormal priority that split one composite into horizontal row strips (the caller runs a strip too); started lazily, runs inline below 64 rows. UNIT 5 — do NOT demote the ThreadPool from Parallel.For, it leaks into unrelated work (see ai.md "Render budget") |
FrameCadence.cs |
Pure arrival-cadence counter (FrameCadence + FrameCadenceSummary.Describe): frames, gaps, hitches, longest/avg gap for a window of a timestamped stream. Fed by MediaCaptureFrameSource (camera stamps vs our arrivals) and FramePump (distinct webcam Epochs that reached the output) — UNIT 0, see ai.md "Frame cadence" |
Related: Themes/Controls.xaml styles the lists this
class backs; Models/index.md and
ViewModels/index.md for the objects it works on.