v1.5: 2-tab dialog + scanlines + glow toggle + custom color picker#6
Merged
Conversation
New feature spec for the v1.5 dialog overhaul: - Property-sheet dialog with Visuals + Performance tabs (no Apply button) - CRT scanlines (enable + intensity + continuous style slider) - Glow Enabled toggle (separate from Glow intensity) - Custom color picker (ChooseColorW + persistent palette) - Live FPS/GPU% in Performance tab title - Fade-timer feature removal Includes the requirements checklist and clarifications session resolving 5 questions covering palette persistence on Cancel, Custom re-selection UX, tab title format, checkbox-vs-Win11-toggle (deferring the pill visual), and OK/Cancel/X dismissal semantics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolves 9 planning concerns: PropertySheetW modeless integration, runtime tab-title updates via TabCtrl_SetItem, lock-free FPS publisher, CBN_SELENDOK re-click handling, ChooseColorW palette persistence, scanline shader cbuffer layout + placement, cross-tab disabled-state propagation, fade-timer removal inventory, and the locked tab-title format string. Constitution check passes (all 9 principles). Suggested phase ordering: foundational scaffolding -> US4 fade-timer removal -> US1 property-sheet structure -> US2 Glow Enabled -> US3 Scanlines -> US5 Custom color -> US6 docs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Phase ordering: foundational (T001-T007) -> US4 fade-timer removal (T008-T020) -> US1 property sheet (T021-T033) -> US2 Glow Enabled (T034-T044) -> US3 Scanlines (T045-T055) -> US5 Custom color (T056-T066) -> US6 CHANGELOG (T067) -> Polish (T068-T075). Pairs every test task with its impl task; Phases 4-6 can run concurrently after Phase 3 closes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
C1 — Format-string contradiction: switch to 'use 0 instead of --' (user decision). FR-012, edge case #1, clarifications, T032, research R9, contracts/propertysheet.md, contracts/fps-publisher.md, quickstart.md all updated to reflect uniform '%u'/'%u' format with 0-on-unavailable. C2 — Phase 2 priority header: now reads 'P3 — sequenced as P1 dependency' so triage tools don't misclassify fade-timer removal. C3 — OK-commit coverage gap: added T033a (CommitLiveMode impl) and T033b (CommitLiveMode_WritesAllV15Fields test). C4 — CBN_SELENDOK reliability: switched primary trigger to subclass-based WM_LBUTTONUP/WM_KEYUP + CB_GETDROPPEDSTATE watcher (reliable across themes). FR-029, R4, T064, plan.md, quickstart.md all updated. CBN_SELENDOK kept as optional belt-and-braces secondary trigger. C5 — quickstart FR citation: FR-039..FR-043 -> FR-036, FR-037 for fade-timer removal. C6 — plan.md FR list for registry-value additions: FR-036/FR-037 removed (they are removals), FR-020 added (it is an addition). C7 — test-name alignment: LegacyShowFadeTimers_PresentValueDoesNotPreventLoad -> LegacyShowFadeTimersIsSilentlyIgnored to match T010. C8 — tasks SC citation fix: T049, T055 SC-011 -> SC-013 (default-on-scanlines is SC-013, not the OK/Cancel SC-011). C9 — plan.md atomics: now correctly lists both m_publishedFps + m_hasPublishedFps. C10 — research.md polish: dropped stream-of-consciousness 'Wait — re-reading' paragraph. C11 — FR-028d folded into FR-028 with CHANGELOG mandate as a sub-clause; FR-028a/b/c kept distinct. C12 — SC-007 regression coverage: added T072a (V14SettingsRegressionTests round-trip). C13 — Custom… ellipsis: explicit note in acceptance scenario that selected-item display matches drop-list label (no string mutation on selection). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ole mechanism Simplifies the Custom-color same-item commit detection to a single mechanism (subclass-based CB_GETDROPPEDSTATE watcher on WM_LBUTTONUP / WM_KEYUP). The CBN_SELENDOK belt-and-braces secondary trigger added in 99a2e38 is removed — the subclass watcher reliably covers every UI input path on its own. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…codebase Drift caught by impl agent pre-flight + explore-agent cross-check: 1. ColorScheme enum: spec invented Green/Amber/White/Cycle/Custom=4. Reality is Green/Blue/Red/Amber/ColorCycle (v1.4 ordinals 0..4). Per FR-039 / SC-007 the v1.4 ordinals MUST be preserved. Append Custom = 5 instead. Drop all White references. 2. Color combo control id: spec/tasks used IDC_COLOR_COMBO; the existing v1.4 id is IDC_COLORSCHEME_COMBO. No new id needed. Updates spec.md (US4 prose + acceptance scenario + FR-006), data-model.md §2, tasks.md (T001, T056, T059, T063). research.md and contracts already use the correct names. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- resource.h: IDD_VISUALS_PAGE, IDD_PERFORMANCE_PAGE, IDT_PERF_TITLE_TIMER, IDS_VISUALS_TAB_TITLE, IDS_PERFORMANCE_TAB_TITLE_INITIAL, IDS_PERFTAB_TITLE_FORMAT, IDC_GLOW_ENABLED_CHECK, scanlines control IDs - MatrixRain\pch.h: add <commdlg.h> and <prsht.h> - MatrixRainCore\Shaders\: create with .gitkeep for scanlines.hlsl T001, T002, T003
Maps the v1.5 Scanlines Style slider value (1..100) to the number of scanlines that span the full output height: lines = 1000 * 0.15^(style / 100) Endpoints clamp to [1, 100]. Reference values: style=1 -> ~981 lines (densest) style=50 -> ~387 lines (default) style=100 -> ~150 lines (sparsest, retro CRT) Includes 7 unit tests covering the 5 reference points plus clamp invariance at both endpoints. T004, T005, T006, T007
Excises the orphaned fade-timer debug overlay from production and tests top-to-bottom so the dialog rewrite in later phases operates on a clean slate. Production removals: - ScreenSaverSettings: m_showFadeTimers field - ApplicationState: ToggleDebugFadeTimes, SetShowDebugFadeTimes, GetShowDebugFadeTimes, RegisterShowDebugFadeTimesCallback, m_showDebugFadeTimes, m_showDebugFadeTimesChangeCallback - SharedState: showDebugFadeTimes (live + snapshot) - RenderParams: showDebugFadeTimes - RenderSystem: RenderDebugFadeTimes decl + impl + call site - ConfigDialogController: UpdateShowFadeTimers - MatrixRain.rc: 'Show fade timers' control - resource.h: IDC_SHOWFADETIMERS_CHECK - ConfigDialog.cpp: OnShowFadeTimersCheck + every dispatch site - MonitorRenderContext.cpp: snapshot.showDebugFadeTimes plumbing - Application.cpp: callback registration + initial sync + WM_KEYDOWN sync Test removals: - ConfigDialogControllerTests: 6 fade-timer references - ScreenSaverSettingsTests: defaults assertion New test: - RegistrySettingsProviderTests::LegacyShowFadeTimersIsSilentlyIgnored pre-writes the legacy REG_DWORD, asserts Load() succeeds (FR-037) Grep for fadetimer/fade.timer/showFadeTimes/DebugFadeTimes is clean outside the legacy-ignore test. T008-T020
Red for T023. Asserts default 0.0f/false, that PublishFps updates both atomically, and that republish overwrites the value while keeping the has-published flag latched. Targets the lock-free pair consumed by the property-sheet 1 Hz title timer (FR-010, contracts/fps-publisher.md).
Adds m_publishedFps + m_hasPublishedFps atomics on MonitorRenderContext with PublishFps() / GetPublishedFps(bool&) / HasPublishedFps() accessors and wires the render thread to publish each frame after FPSCounter::Update(). Memory order is relaxed on both sides per contracts/fps-publisher.md and research.md R3 — single-float atomic is hardware-atomic on x64/ARM64, the consumer reads at 1 Hz against a ~60 Hz producer so a stale read is at most ~16 ms (invisible to a human watching a 1 Hz display). Green for T021.
…S1 T022) Red for T024/T025/T026: asserts EnterLiveMode snapshots and CancelLiveMode restores all 5 new v1.5 rollback-eligible fields (glowEnabled, scanlinesEnabled, scanlinesIntensity, scanlinesStyle, customColor). Palette is intentionally NOT in the snapshot per FR-035.
…1 T024-T027)
Adds the 5 rollback-eligible v1.5 settings (glowEnabled, scanlinesEnabled,
scanlinesIntensity, scanlinesStyle, customColor) plus the FR-035 carve-out
palette to ScreenSaverSettings. ConfigDialogController gets the five new
Update* setters (Green for T022) and CancelLiveMode now preserves the
live palette across the snapshot restore to enforce FR-035 at the
controller boundary. SharedState carries 5 atomic live fields + 5
snapshot mirrors plus their copy in the lock-free Snapshot struct, and
MonitorRenderContext::Render fills RenderParams.{glowEnabled,
scanlinesEnabled, scanlinesIntensity, scanlinesLineCount, customColor}
from the snapshot via ScanlineLineCount(style) for the line count.
All 422 x64 Debug tests pass (was 420; +2 from T022 snapshot tests).
Red for T033a. Verifies the property-sheet OK button's PSN_APPLY path (via CommitLiveMode) Save()s every rollback-eligible field — the 5 new v1.5 fields and 3 representative v1.4 fields — to the settings provider and clears live-mode state.
Green for T033b. Wraps ApplyLiveMode() under the spec-mandated name called from the property-sheet OK button's PSN_APPLY notification.
…033) Splits IDD_MATRIXRAIN_SAVER_CONFIG into IDD_VISUALS_PAGE (Density, Speed, Glow Intensity/Size, Color combo, Scanlines groupbox placeholder, Start Fullscreen) and IDD_PERFORMANCE_PAGE (GPU adapter, Glow Enabled placeholder, Quality preset, Glow passes/resolution/smoothness, multi- monitor, debug, Reset to defaults), wires them into PropertySheetW with PSH_PROPSHEETPAGE|PSH_NOAPPLYNOW|PSH_PROPTITLE|PSH_USECALLBACK plus PSH_MODELESS for the live-overlay path (FR-001, FR-002, FR-013, contracts/propertysheet.md). Page proc is shared (PageDlgProc) — control handlers dispatch by ID so each page only processes its own controls. Per-page tooltip is keyed off SetPropW(kPageTooltipProp); the info-button font is one shared HFONT on DialogContext, created on whichever page initialises first. OK/Cancel are now frame-owned buttons; PSN_APPLY/PSN_RESET notifications translate to CommitLiveMode / CancelLiveMode with idempotency guards on DialogContext.m_applied / m_rolledBack so the second page to see each notification is a no-op (FR-004a, T033, contracts/propertysheet.md). PSCB_INITIALIZED installs a 1 Hz TimerProc (kPerfTitleTimerId) that reads the primary monitor's MonitorRenderContext::GetPublishedFps() and the shared PDH GPU% counter (Application::GetPrimaryRenderContext + QueryProcessGpuLoadPercent), formats via IDS_PERFTAB_TITLE_FORMAT, and calls TabCtrl_SetItem on page index 1 (FR-009, FR-010, FR-011, FR-012, contracts/propertysheet.md). Frame centering moved from per-page OnInitDialog to PSCB_INITIALIZED — the sheet is what the user sees. All 423 x64 Debug tests pass. Manual smoke test for T031/T033 dismissal semantics deferred to next UI walkthrough.
…034-T036) Red for T038/T039. Defaults_GlowEnabledIsTrue covers FR-038; the two RegistrySettingsProvider tests cover the GlowEnabled REG_DWORD path (round-trip both states + missing-defaults-ON); ShouldRunBloomPass is the unit-testable decision predicate the RenderSystem render loop consults instead of the legacy 'intensity == 0' branch (FR-015).
Green for T034/T035/T036. m_glowEnabled was added to ScreenSaverSettings early as part of T024 (Phase 3); now wire it through the registry (VALUE_GLOW_ENABLED, REG_DWORD, defaults to 1) and through RenderSystem via the unit-testable ShouldRunBloomPass helper, which replaces the legacy 'm_glowIntensity > 0.0f' bypass branch. Bloom resources stay allocated when bypassed so re-enabling is instant. All 427 x64 Debug tests pass (+4 from US2 Red tests).
Reverts MIN_GLOW_INTENSITY_PERCENT 0 -> 1 (T040, FR-014); enables the IDC_GLOW_ENABLED_CHECK on the Performance page; removes the legacy '0% (glow disabled)' label branch from FormatPercentLabel (T041). ApplyGlowEnabledUI(hSheet, enabled) (T042, research.md R7) walks both pages via PropSheet_IndexToHwnd and EnableWindow's the Glow Intensity + Glow Size trios on Visuals and the Quality Preset + Glow Passes / Resolution / Smoothness trios on Performance. Both pages' WM_INITDIALOG + OnResetButton mirror the loaded m_glowEnabled into the checkbox and drive ApplyGlowEnabledUI; BN_CLICKED on IDC_GLOW_ENABLED_CHECK calls controller->UpdateGlowEnabled and re-applies the UI. T043 (per-tab tooltip on disabled controls) DEFERRED — Windows tooltips don't fire on WS_DISABLED controls without a transparent parent-relay tooltip per page; greyed-out controls already convey disabled state. Existing TestSaveSettings_ClampsInvalidValues updated to assert against the new MIN constant rather than the literal 0. All 427 x64 Debug tests still pass.
…lity (US3 T045-T047) Red for T049. Defaults_Scanlines* (T045) pin enabled=true / 30 / 50; RegistrySettingsProvider tests (T046) cover round-trip both states, both ints (intermediate values), clamping 0->1 and 200->100 on read for both, and missing-key default fallback (SC-013). T047 pins that quality presets stay orthogonal to scanline settings.
Green for T045/T046/T047. m_scanlinesEnabled/Intensity/Style were added to ScreenSaverSettings in T024 (Phase 3); now wire them through RegistrySettingsProvider as REG_DWORDs (ScanlinesEnabled defaults to 1, Intensity/Style to 30/50 per data-model.md §1). Defensive clamp to [1,100] on write; read-side clamp lives in ScreenSaverSettings::Clamp already covers tampered registries. All 434 x64 Debug tests pass.
Adds the Scanlines groupbox to the Visuals page (Enable + Intensity slider + Style slider, both with info buttons), wires WM_INITDIALOG / WM_HSCROLL / BN_CLICKED, and adds ApplyScanlinesEnabledUI for the intra-page enable/disable propagation when Enable Scanlines toggles. Tooltip infotip strings + IsInfoTipControlId / kInfoIds extended. Ported scanlines.hlsl to MatrixRainCore\\Shaders\\ verbatim from Casso's CRT pack (MIT, upstream crt-pi), with the two MatrixRain-spec modifications baked in: drop the hardcoded kNativeScanlines (line count is now CPU-supplied via g_linesPerHeight per FR-023) and drop luminance gating (FR-024 — scanlines darken uniformly). cbuffer is the 16-byte ScanlineCb per contracts/scanline-shader.md. T051/T052 (RenderSystem post-pass integration + per-frame upload) and T055 (manual /c launch sanity) DEFERRED — settings persist and the shader source is in place; the visual effect requires the post-bloom render pipeline rewire which is a focused follow-up. All 434 x64 Debug tests pass.
- Remove IDC_QUALITY_GROUPBOX wrapper around the quality cluster - Reorder Performance tab top-to-bottom: Use all monitors, Enable glow, GPU combo, Quality cluster (preset + 3 advanced knobs), Show performance metrics; blank rows act as separators - Rename `Render on all monitors` -> `Use all monitors` - Rename `Show debug statistics` -> `Show performance metrics` - Remove page-scoped `Reset to defaults` pushbutton (the next commit hoists it onto the property-sheet frame so it spans both tabs) Registry value names and behavior unchanged. IDC_QUALITY_GROUPBOX and IDC_RESET_BUTTON IDs are orphaned in resource.h but harmless; cleaned up in the follow-up commit that introduces the frame Reset button.
PropertySheets don't expose a native Reset button, so we create one in PSCB_INITIALIZED parented to the sheet frame and intercept its WM_COMMAND/BN_CLICKED in a comctl6 SetWindowSubclass hook. The handler calls controller->ResetToDefaults() and broadcasts a new WM_APP+50 (WM_APP_RESET_RESYNC) message to every page via PropSheet_IndexToHwnd; each page re-reads its own controls from controller->GetSettings() via the page-agnostic ResyncPageFromSettings helper. - ConfigDialogController::ResetToDefaults now pushes the freshly-reset settings through to ApplicationState::ApplySettings in live mode so the live preview snaps back instantly regardless of which page is active (previously the dialog-side OnResetButton had to walk every Update* setter, which only worked when the active page happened to host every control — broken since the Phase-3 dialog split) - SheetFrameSubclass + kSheetFrameSubclassId namespace block relocated above ShowConfigDialog so the modal trampoline can install it too (modeless path already did) - CreateFrameResetButton mirrors the OK button's right margin to the left for footer-left placement; matches OK's font via WM_GETFONT + WM_SETFONT for theme consistency - Page DLGPROC gains a WM_APP_RESET_RESYNC handler that re-reads every control's value from the current settings; GetDlgItem returns NULL for IDs not present on that page so the helper is page-agnostic - Retired the now-broken OnResetButton + page-scoped IDC_RESET_BUTTON (1013); IDC_QUALITY_GROUPBOX (1033) retired in the layout commit - New IDC_RESET_DEFAULTS (1051) for the frame-scope button - Pairs with ConfigDialogControllerTests ResetToDefaults_LiveMode_PushesAllFieldsToSharedState: enter live mode, mutate every settings field, call ResetToDefaults, assert ApplicationState mirrors the defaults (435/435 green on x64 Debug) Auto-bumps tasks.md with a Phase 2.5 section tracking T2.5.1..T2.5.8.
Ports the CRT scanline shader from ..\Casso into the bloom pipeline as a post-composite full-screen pass. The plumbing: - m_postBloomTarget Texture2D + RTV + SRV trio at backbuffer dims; created alongside the bloom resources in CreateBloomResources, released in ReleaseBloomResources (so window resize handles it for free via the existing Release+Recreate pair in Resize) - s_kszScanlineShaderSource inline copy of scanlines.hlsl matching the s_ksz* string pattern every other shader in this file uses; .hlsl on disk stays as documentation + source of truth - CompileBloomShaders gains a soft-fail scanline compile per the analyze decision (FR-028b silent-bypass-on-init-failure): on D3DCompile or CreatePixelShader failure we OutputDebugStringA the error, OutputDebugStringW a session-banner, clear m_scanlinePS, and return S_OK from CompileBloomShaders so the rest of the pipeline boots normally. Render-time checks then route through the bypass branch -- no UI surface, controls stay enabled - CreateScanlineConstantBuffer (16-byte DYNAMIC) called from Initialize after CreateBloomConstantBuffer - UploadScanlineConstants (Map/Unmap WRITE_DISCARD) mirrors RenderParams.scanlinesIntensity (already 0..1) and scanlinesLineCount (already ScanlineLineCount(style)) into ScanlineCb each frame - ApplyBloom signature gains pCompositeTarget so Render can route the composite into m_postBloomRTV (or stay on the backbuffer when scanlines are off) - ApplyScanlinePass binds m_scanlineConstantBuffer at b0, m_samplerState at s0, samples m_postBloomSRV, writes m_renderTargetView - ShouldRunScanlinePass predicate gates on scanlinesEnabled AND glowEnabled per the spec (mini-2.5 sibling decision: scanlines need bloom to be running since they share the post-bloom-target plumbing). Render also checks m_scanlinePS / m_postBloomRTV / m_postBloomSRV liveness so the soft-fail bypass is the predicate's silent partner New test: RenderSystemScanlineBypassTests covers the 4 predicate cases (both-on, scanlines-off, glow-off, both-off) + the 16-byte ScanlineCb layout sanity match against the HLSL b0 register. x64 Debug: 440/440 (was 435; +4 predicate + 1 layout).
When the user toggles Glow Enabled OFF, the dialog already greys every glow-dependent control on both tabs (T042 ApplyGlowEnabledUI). But a greyed control is a passive signal -- the user can't tell why it's disabled or where the toggle lives. T043 adds explanatory tooltips: - 'Glow is disabled on the performance tab.' on the Visuals-tab intensity/size trios (points users at the toggle's actual location) - 'Glow is disabled.' on the Performance-tab quality + advanced trios (toggle is on the same page so the wording is just a confirmation) Implementation per research.md R4 + the user-supplied parent-relay hint: register rect-based TTF_SUBCLASS TOOLINFO entries on the parent dialog (one per IDC_* in IsDisabledGlowTooltipId's catalogue) inside CreateAndRegisterTooltip. Disabled child controls don't fire WM_MOUSEMOVE so events bubble to the parent and the rect tool catches them. When glow is ON, controls consume their own mouse events and these rect tools never activate (silently dormant -- no need to gate in the text callback). uId is the IDC_* directly (not the HWND) so the TTN_NEEDTEXTW branch in PageDlgProc can recognise the sentinel without an HWND-to-id round trip. Tab disambiguation: probe GetDlgItem(IDC_GLOWINTENSITY_SLIDER) -- present only on the Visuals page. No new tests: T043 is thin Win32 UI plumbing exempt per the constitution carve-out (tasks.md preamble). x64 Debug: 440/440 green.
Adds Custom variant at ordinal 5, past v1.4 ordinals Green=0 / Blue=1 / Red=2 / Amber=3 / ColorCycle=4 which MUST stay frozen so existing registry values continue to round-trip after a v1.5 upgrade per FR-039 + SC-007. Parse/Serialize/IsValid recognise the L"custom" key. GetNextColorScheme remains modulo-(ColorCycle+1) so Custom is NOT in the hotkey cycle -- opt-in via the combo dropdown only per FR-039. GetColorRGB(Custom, ...) falls through to the default-green branch on purpose; T062 will special-case Custom in the render path to pull from RenderParams.customColor. Until then, selecting Custom shows green (no visible regression -- the v1.4 combo doesn't even expose Custom). Pairs with 4 new ColorSchemeTests cases: ordinal value, v1.4-ordinals- frozen, key round-trip, hotkey-cycle skip. 444/444 on x64 Debug.
Two new registry values per contracts/registry-schema.md: - CustomColor (REG_DWORD): the active user-picked streak color, written unconditionally on every Save so an existing custom RGB survives even when the user is currently on a non-Custom scheme. Absent on load leaves m_customColor at its in-class default (RGB(0,255,0)) so the ChooseColor dialog opens pre-populated on first invocation. - CustomColorPalette (REG_BINARY, exactly 64 bytes = 16 COLORREFs): matches CHOOSECOLORW::lpCustColors layout so the dialog wiring in T065 can memcpy directly to/from the settings struct. Persists UNCONDITIONALLY across live-mode rollback per FR-035 (palette lives outside the snapshot rollback set, tested explicitly in T058). Anything other than exact-64-bytes on read -- including absent -- zero-fills the palette per FR-038's size-mismatch rule. Three new helper signatures on RegistrySettingsProvider: ReadDword, WriteDword, WriteBinary. ReadDword is distinct from ReadInt in its out-type (DWORD vs int) -- COLORREF semantics are 32-bit-unsigned so treating it as signed int would round-trip incorrectly for any value with the high blue-channel bit set. 5 new RegistrySettingsProviderTests cases: - CustomColorRoundTrip - CustomColorAbsentMeansChooserDefault - CustomColorPaletteRoundTrip - CustomColorPaletteSizeMismatchYieldsZeroes (writes 32 bytes manually, expects zero-fill on read) - MissingCustomColorPaletteYieldsZeroes 449/449 on x64 Debug (was 444).
CancelLiveMode already implements the FR-035 palette carve-out (landed earlier under T024's settings groundwork) -- this commit adds the pair of unit tests that lock the behaviour: - CustomColorRollsBackOnCancel: UpdateCustomColor during a live session, then Cancel -- the active CustomColor reverts to the snapshot value taken at InitializeLiveMode time (FR-004) - CustomColorPaletteIsNOTRolledBackOnCancel: SetCustomColorPalette with 16 distinct slots, then Cancel -- every slot must survive because the palette lives OUTSIDE the snapshot rollback set (FR-035 carve-out, tested explicitly here so future refactors can't silently regress the rollback boundary) Adds inline SetCustomColorPalette setter on ConfigDialogController since the chooser-dialog wiring (T065) needs a way to write the palette back after the user OKs ChooseColorW; the setter does NOT propagate to ApplicationState because the palette isn't a render parameter, only feeds CHOOSECOLORW::lpCustColors. 451/451 on x64 Debug (was 449).
…(US5 T062) RenderSystem - UpdateInstanceBuffer takes a customColor COLORREF in addition to the ColorScheme. When colorScheme == ColorScheme::Custom we override the static-palette GetColorRGB() result with a Color4 built from the COLORREF's R/G/B channels normalised to [0..1]. Other schemes pass through unchanged (per FR-033/FR-034). - Caller in Render passes params.customColor (sourced upstream from RenderParams which is sourced from the SharedState snapshot). The wiring under it -- previously a dangling dead end: Saved registry -> ApplicationState::m_settings -> [GAP] -> SharedState The atomics (liveGlowEnabled, liveScanlinesEnabled, liveScanlinesIntensity, liveScanlinesStyle, liveCustomColor) existed since v1.5 Phase 3 but nothing ever wrote them, so the render thread kept reading the in-struct defaults regardless of dialog activity OR persisted settings. Selecting Custom in the combo would have rendered green forever. Three additions close the gap: - ApplicationState gains RegisterV15LiveCallback + bulk dispatch from ApplySettings -- one callback for all five v1.5 fields since the UpdateGlowEnabled / UpdateScanlines* / UpdateCustomColor setters all funnel through ApplySettings anyway - Application.cpp wires the lambda to atomic-store every live* field; lock-free path (atomics, NOT the SharedState mutex) so the dialog thread never blocks the render thread on settings churn - Application.cpp also bootstrap-seeds the live atomics from the freshly-loaded settings during init, so the first frame renders the user's persisted values rather than the in-struct defaults 451/451 on x64 Debug -- no test changes since the atomics-wiring is test-equivalent to the existing ApplySettings live propagation that already pushes density / animationSpeed / glowIntensity / glowSize.
- Reposition Reset whenever the property sheet moves OK into its final footer location - Keep the existing posted reposition path as a fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Strip WS_EX_CONTEXTHELP from the sheet frame once it is initialized - Refresh the non-client frame so the caption updates immediately Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Cache the last performance tab title on the dialog context - Avoid TabCtrl_SetItem when the formatted text did not change Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add vertical padding around the scanlines enable checkbox - Expand the Visuals page and scanline group to preserve footer spacing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use PropSheet_IsDialogMessage for modeless sheet accelerators - Include prsht.h through the core precompiled header for the macro Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Propagate live fullscreen checkbox changes into ApplicationState settings - Preserve the live property sheet while render windows rebuild for mode changes - Add coverage for live fullscreen propagation to display mode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lines moved to perf tab 1. Reset button now anchors its left edge to the tab control's left edge (via PropSheet_GetTabControl). Previous logic mirrored OK's right margin from the right edge of the sheet, which put Reset far inboard rather than at the dialog's content column. 2. After Application::RebuildContextsForCurrentMode (triggered by Start In Fullscreen toggle, multimon toggle, GPU change), the newly-created primary render window sits at the top of the Z-order. The config dialog falls behind it. SetWindowPos(HWND_TOP, ..., SWP_NOACTIVATE) restores the dialog to the top while preserving keyboard focus. 3. Color combo and GPU combo widths shrink from w=195 to w=162 to match the trackbar widths (right edge x=252) above/below them. 4. Scanlines section moved from Visuals tab to Performance tab, immediately below Enable glow. Visuals tab shrinks (h=225 -> h=150); Performance tab grows (h=215 -> h=268). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ntrancy PSN_APPLY/PSN_RESET handlers were calling DestroyWindow(GetParent(hDlg)) synchronously while still inside comctl32's notification dispatch. comctl32 iterates pages internally during PSN_APPLY/PSN_RESET and does not expect the sheet to be torn down mid-iteration; doing so corrupts dispatch state and recursively re-enters the frame subclass through WM_DESTROY-driven message traffic, manifesting as a stack overflow inside PropSheet_IsDialogMessage when the user clicks OK or Cancel. Replace the direct DestroyWindow with PostMessage(hSheet, WM_CLOSE, 0, 0). comctl32 unwinds cleanly, and the sheet's default WM_CLOSE handler (which IS the modeless tear-down path it expects) runs on the next message-loop iteration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tPageHwnd Replaces the broken page-level destroy with the documented pattern from PropSheet_GetCurrentPageHwnd: > If the user has clicked the OK or Cancel button, this macro returns NULL. Pages no longer attempt to destroy the sheet during PSN_APPLY/PSN_RESET (which re-entered the frame subclass through cascading WM_DESTROY traffic and overflowed the stack). They simply acknowledge the notification and let comctl32 mark the sheet closed. The message loop in Application::Run polls PropSheet_GetCurrentPageHwnd after each PropSheet_IsDialogMessage call and DestroyWindow's the sheet from outside any notification dispatch. SetConfigDialog(nullptr) and PostQuitMessage continue to run from the sheet frame's WM_DESTROY subclass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rs on Visuals Misread of prior instruction: moved the whole scanlines section (checkbox + intensity slider + style slider). Correct: only the Enable scanlines checkbox sits on Performance (alongside Use all monitors and Enable glow). The intensity and style sliders stay on the Visuals page below Color, where the rest of the per-monitor visual sliders live. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. Move live FPS / GPU%% from the Performance tab title text into a bottom-right static text control (IDC_FPS_GPU_READOUT) present on both pages. The tab title stays a constant 'Performance' so the tab control never has to relayout. PerfTitleTimerProc now writes the readout via GetDlgItem on each page's HWND instead of TabCtrl_SetItem. Format string simplifies from 'Performance (%u fps, %u%% GPU)' to '%u fps, %u%% GPU'. 2. Move the sheet-centering pass from PSCB_INITIALIZED to the posted WM_APP_REPOSITION_RESET handler. PSCB_INITIALIZED fires before comctl32 resizes the sheet to fit the pages, so GetWindowRect there returns the small-template size and the centering math is off. The posted message lands after the layout pass and computes correct (x, y) over the main window rect. 3. Grow both pages to a common template height (h=232) so the readout sits at the same y on both, and so comctl32 doesn't insert dead space below the Visuals controls. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. The ColorComboSubclassProc was firing the Custom-color chooser every time the dropdown was opened while Custom was already selected (wasOpen check happened before DefSubclassProc, but on a button-down/up that just dropped the list open the state was already TRUE). Net effect: once Custom was selected, the dropdown was unusable -- picking any preset re-opened the picker. Subclass removed; CBN_SELCHANGE is now the only chooser trigger. The 'edit current Custom color' path is no longer one-click, but the dropdown is usable again, which matters more. 2. New owner-draw color swatch (IDC_COLOR_SWATCH, SS_OWNERDRAW | SS_SUNKEN) placed at x=257, y=101, w=28, h=12 next to the Color combo on the Visuals page. WM_DRAWITEM in PageDlgProc paints it solid-filled with the resolved RGB: - Static schemes (Green/Blue/Red/Amber): GetColorRGB lookup. - ColorCycle: GetColorRGB with elapsedTime = GetTickCount64()/1000; a 30Hz timer (IDT_COLOR_CYCLE_TIMER) invalidates the swatch each tick so it animates at roughly the same rate as the rain (60s full cycle, matching SC-012). - Custom: ScreenSaverSettings::m_customColor. Timer is started/stopped from OnInitDialog, OnColorSchemeChange, WM_APP_RESET_RESYNC, and WM_APP_OPEN_CUSTOM_COLOR_CHOOSER so it tracks the active scheme. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ombo - Change IDC_COLOR_SWATCH from static (SS_OWNERDRAW) to button (BS_OWNERDRAW) so we get click handling for free and tab-stop participation. - Wire BN_CLICKED to PostMessage(WM_APP_OPEN_CUSTOM_COLOR_CHOOSER) so a single click on the swatch opens the chooser regardless of the active scheme. On OK the chooser switches to Custom and persists the picked colour. - Adjust position to y=100, h=12 to line up with the closed combo edit-area baseline (was y=101 with SS_SUNKEN inset that visually pushed the swatch down a pixel). - Add 1px black FrameRect + focus-rect on ODS_FOCUS so the BS_OWNERDRAW button reads as a swatch (BS_OWNERDRAW provides no native border). - Use GetStockObject(BLACK_BRUSH) cast to HBRUSH (the windowsx.h GetStockBrush macro is not visible in the ARM64 build's PCH transitive includes). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetColorRGB has no entry for ColorScheme::Custom (5 >= __StaticColorCount=4) and falls back to green. The help/hotkey/usage overlays (Settings/Help/Exit hint, key list, /? dialog) all source their text colour from MonitorRenderContext via that lookup, so they rendered green even when the rain itself was a custom colour like magenta. Resolve custom-scheme overlays from snapshot.customColor (COLORREF -> Color4) before calling Update(), mirroring what RenderSystem already does for the rain instance buffer. Static schemes and ColorCycle keep going through GetColorRGB unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ShouldRunScanlinePass was AND-ing scanlinesEnabled with glowEnabled — disabling glow silently disabled scanlines because the no-glow render branch wrote scene-to-backbuffer directly and skipped the m_postBloomTarget intermediate the scanline PS samples from. User expectation is that the two toggles are orthogonal. Drop the glow gate from ShouldRunScanlinePass. Restructure RenderSystem::Render so the no-glow direct-scene copy also targets m_postBloomTarget when scanlines are wanted, then runs ApplyScanlinePass to composite scene + scanlines into the backbuffer. UploadScanlineConstants is hoisted above the bloom branch so both paths see fresh cbuffer values. Update RenderSystemScanlineBypassTests: ScanlineBypassedWhenGlowDisabled becomes ScanlineRunsWhenGlowDisabled with inverted assertion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ResolveSwatchColor was using GetTickCount64()/1000 as the cycle phase, but the rain itself uses ApplicationState::GetElapsedTime() (a frame-accumulated clock that starts at zero when the app launches). The two clocks drift apart by the elapsed startup time, so the swatch displayed a different cycle position than the rain at any given moment. Switch the Cycle branch to read elapsedTime from the running ApplicationState (via GetApplicationFromDialog). Fall back to the tick-count clock only when no Application is attached (CPL preview without a live rain instance). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nlines-glowtoggle # Conflicts: # MatrixRainCore/RenderSystem.cpp # MatrixRainCore/Version.h
T055 was previously deferred because the doc framed it as a manual /c launch. The actual property under test (a v1.4 registry hive with NO v1.5 keys loads cleanly, with every v1.5 field landing at its documented default) is straightforward to automate. New V14HiveWithNoV15Keys_LoadsWithV15Defaults: writes only v1.4-shape values directly via RegSetValueExW (bypassing Save, which would emit v1.5 values too), then loads via the production provider and asserts: - v1.4 fields preserved verbatim - glowEnabled defaults true (FR-038) - scanlinesEnabled defaults true (SC-013 — the intentional break from no-visible-change-on-upgrade) - scanlinesIntensity / scanlinesStyle default to DEFAULT_* constants - customColor defaults to DEFAULT_CUSTOM_COLOR (RGB(0,255,0)) - customColorPalette zero-initialised (no saved palette) 456 tests pass on x64. ARM64 Debug builds clean.
Three findings from the principal-engineer review pass: 1. (High) ResetToDefaults() was zeroing m_customColorPalette by assigning a default-constructed ScreenSaverSettings (its palette is a default-init std::array<COLORREF,16> = all zeros). In live mode the zeroed palette propagated through ApplySettings; Reset->OK then persisted the zeros to the registry, silently destroying the user's 16 saved swatches. FR-035 carves the palette out of all rollback/reset behaviour. Fix: capture m_settings.m_customColorPalette before the reset, restore it on the freshly defaulted struct, and mirror onto the snapshot so a subsequent CancelLiveMode (which preserves the live palette across snapshot restore) still does the right thing. New regression test ResetToDefaultsPreservesCustomColorPalette pins the FR-035 behaviour so a future refactor can't regress it. 2. (Med) WM_APP_REPOSITION_RESET and WM_APP_OPEN_CUSTOM_COLOR_CHOOSER both expanded to WM_APP+51. Worked today only because they target different HWNDs (sheet vs. page) and are handled in different procs, but any future cross-target send would dispatch the wrong handler. Moved WM_APP_OPEN_CUSTOM_COLOR_CHOOSER to WM_APP+52 and grouped its #define alongside the others at file top so the duplicate-define warning would catch future collisions. 3. (Low) PropertySheetW returns -1 on modeless failure. The previous code cast that to HWND and ran it through CWRA, which accepts the non-null (HWND)-1 value as success; context.release() then leaked the DialogContext (controller, GDI font) and the caller would later pass (HWND)-1 to IsWindow/SetWindowLongPtrW. Fix: capture the INT_PTR, gate on (rc != -1 && rc != 0) via CBRAEx, then cast. Failure path now correctly hits the Error: cleanup which leaves the unique_ptr owning the context for RAII destruction. 457 tests pass on x64 (was 456). ARM64 Debug builds clean.
Per user convention: never put anything under [Unreleased]; use the actual version number at the time of the change. Splits the accumulated entries into [1.4.2098] (the v1.4 release content that shipped via PR #5) and [1.5.2161] (this PR's content). Drops the redundant '-- v1.5' suffixes since the section header now scopes them, adds a Fixed subsection for the bugs caught in pre-PR review (palette wipe, stale-SRV, WM_APP collision, PropertySheetW failure handling), and adds a few missed v1.5 entries (per-page FPS/GPU readout, modeless teardown via PropSheet_GetCurrentPageHwnd, Z-order fix on rebuild, overlay text custom-color resolution).
Contributor
There was a problem hiding this comment.
Pull request overview
This PR implements the v1.5 settings UX + rendering additions: a 2-tab modeless property sheet (Visuals/Performance), a scanlines post-process, a glow enable toggle that fully bypasses bloom, and a Custom color picker/palette with live preview plumbing. It also extends registry persistence + live-mode rollback contracts and adds/updates unit tests to lock the behavior down (including v1.4 regression/migration coverage).
Changes:
- Replace the legacy single-page config dialog resource layout with two property-sheet pages and supporting resource IDs/strings.
- Add scanlines rendering plumbing (shader + render-path routing) and new settings fields/registry keys (glowEnabled/scanlines*/customColor*), plus migration/regression tests.
- Remove fade-timer debug overlay plumbing and update docs/release notes for the v1.5 feature set.
Reviewed changes
Copilot reviewed 55 out of 56 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| specs/007-dialog-tabs-scanlines-glowtoggle/research.md | Phase 0 implementation research for property sheet lifecycle, scanlines, custom color picker, and migration/removals. |
| specs/007-dialog-tabs-scanlines-glowtoggle/quickstart.md | Manual QA walkthrough and suggested task ordering for implementing/validating v1.5 features. |
| specs/007-dialog-tabs-scanlines-glowtoggle/plan.md | Implementation plan outlining architecture, constraints, and phased delivery. |
| specs/007-dialog-tabs-scanlines-glowtoggle/data-model.md | Defines new v1.5 settings fields, defaults, rollback eligibility, and render/registry mapping. |
| specs/007-dialog-tabs-scanlines-glowtoggle/contracts/scanline-shader.md | CPU↔GPU contract for scanline shader constants and render ordering. |
| specs/007-dialog-tabs-scanlines-glowtoggle/contracts/registry-schema.md | Registry schema additions for v1.5 and legacy handling expectations. |
| specs/007-dialog-tabs-scanlines-glowtoggle/contracts/propertysheet.md | Property sheet flags/layout contract and dismissal semantics. |
| specs/007-dialog-tabs-scanlines-glowtoggle/contracts/fps-publisher.md | Lock-free FPS publishing contract from render thread to dialog UI. |
| specs/007-dialog-tabs-scanlines-glowtoggle/checklists/requirements.md | Spec-quality checklist for completeness/readiness. |
| README.md | Adds v1.5 highlight row to the version summary table. |
| CHANGELOG.md | Adds a v1.5 release section and documents key UX/rendering changes and migration note; moves v1.4 into a numbered entry. |
| .specify/feature.json | Points “current feature” to the 007 spec directory. |
| .github/copilot-instructions.md | Updates the “SPECKIT” pointer to reference the 007 plan/contracts/quickstart. |
| MatrixRain/resource.h | Adds property-sheet page IDs, timers, strings, and new control IDs; reserves removed IDs. |
| MatrixRain/MatrixRain.rc | Adds string resources and defines the two property-sheet page templates + new controls/readout. |
| MatrixRain/pch.h | Adds commdlg/prsht includes for ChooseColor/property sheet usage in the UI layer. |
| MatrixRainCore/pch.h | Adds <Prsht.h> for property-sheet integration used by core Application message loop. |
| MatrixRainCore/Version.h | Updates VERSION_BUILD (should not be committed; see review comments). |
| MatrixRainCore/SharedState.h | Adds live atomics + snapshot mirrors for new v1.5 settings fields; removes fade-timer flag. |
| MatrixRainCore/RenderParams.h | Adds per-frame params for glowEnabled/scanlines/customColor and derived scanline values; removes fade-timer flag. |
| MatrixRainCore/ScreenSaverSettings.h | Adds v1.5 settings fields/defaults and clamps; restores glow intensity min to 1; removes fade-timer field. |
| MatrixRainCore/RegistrySettingsProvider.h | Adds new v1.5 registry value names + DWORD/BINARY helpers for custom color/palette. |
| MatrixRainCore/RegistrySettingsProvider.cpp | Implements v1.5 load/save of glow/scanlines/custom color + palette, with clamping and legacy behavior. |
| MatrixRainCore/ScanlineStyleMapping.h | Declares style→line-count mapping helper API. |
| MatrixRainCore/ScanlineStyleMapping.cpp | Implements style→line-count mapping (clamp + pow curve). |
| MatrixRainCore/Shaders/scanlines.hlsl | Adds scanlines pixel shader source (attributed) for CRT-style darkening effect. |
| MatrixRainCore/Shaders/.gitkeep | Keeps the new shader directory present in source control. |
| MatrixRainCore/RenderSystem.h | Adds scanline pass helpers/resources and bloom/scanline decision predicates; updates instance-buffer update signature. |
| MatrixRainCore/RenderSystem.cpp | Implements scanline constant buffer + post-bloom target + scanline pass; adds glow bypass; removes fade-timer render path; tweaks FPS/GPU overlay string. |
| MatrixRainCore/MonitorRenderContext.h | Adds lock-free FPS publisher fields/accessors. |
| MatrixRainCore/MonitorRenderContext.cpp | Publishes FPS from render thread and plumbs scanline/custom-color values into RenderParams; fixes overlay color for Custom scheme. |
| MatrixRainCore/ColorScheme.h | Adds ColorScheme::Custom = 5 with ordinal stability notes. |
| MatrixRainCore/ColorScheme.cpp | Adds “custom” key parsing/serialization and includes it in valid key set. |
| MatrixRainCore/ConfigDialogSnapshot.h | Documents rollback carve-out for the custom color palette in live mode. |
| MatrixRainCore/ConfigDialogController.h | Removes fade-timer setter and adds v1.5 setters + palette setter; adds CommitLiveMode alias. |
| MatrixRainCore/ConfigDialogController.cpp | Implements v1.5 setters, palette preservation through Reset/Cancel, and live reset propagation. |
| MatrixRainCore/ApplicationState.h | Removes fade-timer APIs and replaces per-flag callback with a bulk v1.5 live callback. |
| MatrixRainCore/ApplicationState.cpp | Removes fade-timer state and fires bulk v1.5 live callback from ApplySettings. |
| MatrixRainCore/Application.h | Adds accessor for primary MonitorRenderContext for FPS readout. |
| MatrixRainCore/Application.cpp | Wires bulk v1.5 settings callback into SharedState atomics; switches to PropSheet_IsDialogMessage + canonical modeless teardown polling; preserves config dialog across rebuilds with Z-order fix. |
| MatrixRainCore/MatrixRainCore.vcxproj | Adds ScanlineStyleMapping sources to the core project. |
| MatrixRainCore/MatrixRainCore.vcxproj.filters | Adds ScanlineStyleMapping files to VS filters. |
| MatrixRainTests/unit/V14SettingsRegressionTests.cpp | Adds v1.4 registry round-trip regression and v1.4→v1.5 defaults migration sanity test. |
| MatrixRainTests/unit/ScreenSaverSettingsTests.cpp | Updates defaults test for v1.5 settings and fade-timer removal. |
| MatrixRainTests/unit/ScanlineStyleMappingTests.cpp | Adds unit tests for style→line-count mapping and clamp invariants. |
| MatrixRainTests/unit/RenderSystemBloomBypassTests.cpp | Adds pure-helper tests for bloom pipeline bypass when Glow Enabled is off. |
| MatrixRainTests/unit/RenderSystemScanlineBypassTests.cpp | Adds pure-helper tests for scanline pass decision predicate and ScanlineCb layout (but has a misleading header comment; see review comments). |
| MatrixRainTests/unit/MonitorRenderContextFpsPublisherTests.cpp | Adds unit tests for the FPS publisher fields/accessors. |
| MatrixRainTests/unit/RegistrySettingsProviderTests.cpp | Extends registry tests for v1.5 values (glow/scanlines/custom color/palette) and legacy fade-timer handling; updates glow intensity clamp expectation. |
| MatrixRainTests/unit/QualityPresetsTests.cpp | Asserts preset switching does not touch scanline settings. |
| MatrixRainTests/unit/ConfigDialogControllerTests.cpp | Removes fade-timer tests and adds v1.5 rollback/commit/reset/palette persistence tests. |
| MatrixRainTests/MatrixRainTests.vcxproj | Adds new unit test compilation units for v1.5 coverage. |
UpdateGpuAdapter and UpdateMultiMonitorEnabled mutated only the controller's private settings copy, so the live context rebuild re-resolved the adapter and multi-monitor gate from the stale ApplicationState and recreated the device on the default GPU. - propagate both via ApplicationState::ApplySettings in live mode, matching UpdateStartFullscreen - add regression tests for both live-mode propagations
VERSION_MINOR stayed at 3 while the 1.4 and 1.5 CHANGELOG sections shipped; align the version header with the documented product line.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…docs Address Copilot PR #6 review feedback (docs/comments only; no behavior change): - Scanlines Enabled checkbox lives on the Performance tab, not Visuals; fix quickstart, CHANGELOG, and phase-ordering notes (Intensity/Style sliders stay on Visuals). - CustomColor is persisted unconditionally on every Save (the provider sees only a settings struct and has no per-session chooser-OK signal); update data-model and registry-schema contract to match the shipped, tested behavior instead of the pre-implementation conditional-write text. - Perf FPS/GPU shows in a bottom-right readout on both pages, not the Performance tab title; fix the .rc and quickstart descriptions. - Post-bloom target intentionally uses the scene/bloom R8G8B8A8 format; the scanline pass is a shader draw (not a CopyResource) so the BGRA backbuffer difference is GPU-handled. Clarify the comment. - Scanline pass runs independently of the glow toggle; fix the stale RenderSystemScanlineBypassTests header comment.
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.
v1.5 reshapes the configuration dialog into a 2-tab property sheet (Visuals + Performance), adds CRT scanlines, a glow on/off toggle, a custom color picker with a live swatch, and a per-page live FPS/GPU readout. Built on top of v1.4 (PR #5).
112 commits, 94 files, +12411/-827.
Highlights
Dialog overhaul
PropSheet_GetCurrentPageHwndpolling (the canonical pattern — no re-entrantDestroyWindowfrom notification handlers)PropSheet_IsDialogMessage)WS_EX_CONTEXTHELPstripped)Scanlines (CRT effect, ported from Casso)
m_postBloomTarget→ backbuffer1000 * pow(0.15, style/100))m_postBloomTargetso the scanline PS has a populated SRV regardlessGlow Enabled toggle
EnableWindowacross both pages for the dependent glow controlsCustom color picker
Custom…as combo index 5 in the existingIDC_COLORSCHEME_COMBO(preserves v1.4 ordinals 0-4 per FR-039 / SC-007)ChooseColorWmodal withCC_FULLOPENand 16-swatch persistent palette (REG_BINARY, 64 bytes; unconditional persistence per FR-035 even on dialog Cancel)snapshot.customColor(previously fell throughGetColorRGBto green)Z-order fix
RebuildContextsForCurrentModenow re-asserts the config dialog above the freshly-created render window after the GWLP_HWNDPARENT restoration so toggling Start In Fullscreen doesn't put the rain on top of the dialogMigration
V14HiveWithNoV15Keys_LoadsWithV15Defaults) writes a v1.4-shape hive directly and verifies the v1.5 fields land at their defaultsTest status
457/457 unit tests pass on x64. Both x64 + ARM64 Debug builds clean.
Pre-PR review pass
Three findings from the principal-engineer review were resolved before opening (last commit,
0150bad):WM_APP_*IDs collided onWM_APP+51— separated to +51 and +52PropertySheetWreturning -1 was treated as success — gated onrc != -1 && rc != 0Remaining manual QA
quickstart.mdwalkthrough for US1..US6 — needs GUI on real hardware. Smoke-test before merge.Related
Branched from
006-multimon-gpu-efficiency(PR #5, now merged). Now diffs clean againstmaster.