feat(vk): add selectable CRT monitor and TV filters #109
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!109
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:feat/crt-filters"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
Players who want a CRT picture can choose a clean aperture grille, a curved consumer-TV shadow mask or two lighter scanline looks, without an external injector. Off remains the default. This first slice follows the proposal in #70; its three maintainer questions are still unanswered, so the selection and defaults may change with those answers.
What changed
gpu/vk/vk_menu.cppadds CRT filter and CRT lines immediately after Window scaling filter. They apply without restarting, use the normal options-file reader/writer and do not change the quality preset.gpu/vk/vk_framegen.cppandcrt_policy.hreduce the finished picture to a virtual raster, then run the selected CRT shader. Existing window scaling, sharpness and ultrawide-side treatment come first, including the mandatory downscale when the checked source rectangle is larger than the destination. CRT reads that destination-size result. The game's HUD is filtered; Longshot's menu and overlays come afterwards. Generated frames take the same output path, rather than putting a mask into optical flow; that windowed path still needs runtime testing.When the finished game picture is larger than the destination, the output-effects path first applies main's filtered downscale from #307, even with Window scaling filter Off and Sharpness 0, then feeds that destination-size picture into the CRT virtual raster. This can change fine-detail sampling compared with this pull request's earlier head; the CRT mask and scanlines are still generated at the final destination size, not shrunk afterwards. At
1982a687, windowless captures on Linux with an RTX 4090 and the capture's effects switch on (RX_VK_SHOT_FX=1) showed Off, all four filters and Off again at 1920x1080 to 1920x1080 and at 3840x2160 to 1920x1080: the inspected 1:1 crops keep the expected mask and scanline patterns with no doubled grid and no obvious extra moire. A real window and motion were not tested for this.third_party/crt_shaders/folder. Timothy Lottes' public-domain shader, Hyllian's MIT Fast shader (cgwg's mask; DariusG/hunterk ports), and Greg Hogan / SoltanGris42's GPL-2.0-or-later zfast implementation are pinned to2345ee91602a7404d3a1a6a5df6108d7305fdad4. Compute adaptations stay outside that folder; their differences are documented in its provenance file.Current-main integration: the carry keeps all six existing commits, then adds one separate test/documentation commit. Main's downscale fix in #307 conflicts with the CRT entry path in
gpu/vk/vk_framegen.cpp. The resolution keeps its checked-crop shrink decision and makes it part of the scaling decision: a larger source is filtered to the destination first, then CRT reduces that result to its virtual raster and applies the mask. CRT Off retains main's downscale. Same-size output still avoids the scale pass when no other window effect needs it. Native PQ still scales in 10 bits and bypasses SDR CRT.Main's Intel compiler guard from #252 remains around shader and pipeline creation, including CRT's use of the shared constructor. The resource-failure fixture follows those guarded calls without removing its allocation checks. The options fixture also follows the Plus/Minus definitions from #137 and the real restart-value helper from #136. No production menu behavior or shader algorithm was changed in the separate test commit.
Picture check still needed: shrinking with Window scaling filter Off and Sharpness 0 now passes through main's filtered downscale before CRT, instead of feeding the larger source directly to CRT. The extra stage and destination-sized input can change fine detail and moire. The carried head needs pictures of this combination before publication; the earlier pictures and timings remain evidence only for their named older heads. The order after frame generation and before Longshot overlays is unchanged. Main's results files and every imported shader remain unchanged.
Flags and settings
crt_filtercrt_linesThe remake is not a 240-line game: this deliberately downsamples its finished picture for the selected CRT look. More virtual lines preserve more of its small text. The shader-regeneration helper also has
--glslangto select a compiler and--checkto compare headers rather than write them; ordinary player builds use the checked-in headers.Measurements
Quiet unused-feature cost: 2026-11-13
CRT Off showed no measurable frame-time penalty in this scene. The median of the two run means was 10.648 ms on main and 10.562 ms with this PR, a difference of -0.086 ms (-0.80%). That difference is smaller than either build's repeat spread; it is not a speed-up claim or proof of literally zero cycles. It meets the predeclared no-penalty band of main + max(0.10 ms, 2%); ambiguous or unstable pairs would not pass.
main:
0c74253d4c,v3.0.0-rc.3, sourcecb01b5a3this PR: move/feat/crt-filters-m0c74253d @ 2ad245bd1b7f5c1154090d6d1ece66e484adb769,
v3.0.0-rc.3-5-g2ad245bd1, source9aeb0cbfHardware: RTX 4090, NVIDIA 615.78.08; Core i9-13900K, 94 GiB RAM; Arch Linux 7.2.15-1-omarchy. The later
0b4cd574carry changes documentation, not the measured code.Scene/settings: the same copied Deku Tree save, stationary input at the central web; 1920×1080 output; 50% (960×540), game upscaler; uncapped; sdr; frame generation, v-sync, frame pacing and crt off; game speed per frame; no mods; prepared/warmed caches; timestamp profiler off. Both Linux builds use matching compiler/profile inputs; main was reused, not rebuilt for this pair. Both shader-preparation commands completed, each logging one failed shader program. Their selected scene was then warmed and inspected; the measured phases' complete renderer log blocks reported 0.0 draw omissions and 0.00 shader waits.
Method: A1 main / B1 PR Off / A2 main / B2 PR Off, in one quiet window after a four-minute cooldown. Each process ran for 75 seconds. After observed stable play, 18 seconds let the boot input and scene settle, then a 10-second neutral-input phase supplied the game-present-return intervals. These are CPU-clock present-return intervals, not scan-out, display latency or a labelled CRT GPU span. The GPU column is the existing rounded renderer counter averaged over seven complete 120-present blocks per phase; it is a separate population. Before/after pictures were inspected. The paired worker lasted 309.9 seconds.
At admission, load was 1.86, five-second CPU use 0.586 cores and GPU use 6% (the agreed ceiling was 12%). No other game or compiler was observed. Hyprland, Xwayland, quickshell/qs, tern and one terminal-tooling Chromium GPU process were left running. Each measured phase stayed below the unchanged 3-core mean / 6-core peak other-work limits; each build's repeat spread stayed below 5%. Later load averages include the preceding game and are shown rather than described as idle.
0c74253d0c74253d2ad245bd0c74253d2ad245bdAll four runs were valid, with complete journal coverage and no fatal/device/kernel fault. No present-return gap over one second occurred anywhere in these four traces. B2 did retain one
PlayReporter ... ran without a corescheduler warning near present 10,560, before its measured 13,232–14,172 interval. That warning's two-second wait is not a two-second gap between presents.How to reproduce: use the settings above and the same stationary scene on both builds, warm it first, leave CRT Off, and alternate main/PR/main/PR with the same observed phase. This run also used
RX_VK_PRECOMPILE=0,RX_VK_NO_WARM=1,RX_VK_NO_PREDICT=1,RX_VK_AUTO_PRESET=0,RX_VK_AUTO_TUNE=0andRX_VK_PASS_TIMING=0on both; the Uncapped row was used without anHOS_FPS_MAXoverride.Not measured in this window: any enabled filter, 4K, windowed or generated-frame output, HDR, or synchronized-input pixel equality. Only the unused-feature comparison fit the one complete repeated window; the older per-filter GPU spans below remain diagnostics, not those missing measurements.
Measured run rows (kept here, not in the shared results file)
Branch state: on main
5e072fc8c2adadba5ea610148be1c47800ba0cca; heade94d24624d11798c61ebed51e3f82cd998abc8be; whole game-free suite on this head:546 passed, 2 failed, 16 skipped(main5e072fc8c2adadba5ea610148be1c47800ba0cca:542 passed, 3 failed, 16 skipped). The 2 failing tests fail on main the same way:tests/test_module_ids.py,tests/test_pad_layout.py. The branch was carried onto this main by hand; what differs from a mechanical carry is named in the comment posted with this update.Historical filter-on evidence: the following table and reference pictures retain the actual older commits that produced them. The new unused-feature comparison above is separate; enabled-filter and 4K costs have not been measured in that window.
main:
d725b363(2026-11-12), built asv2.2.0-rc.1-20-gd725b3631, source33703252this PR: move/feat/crt-filters @ b6c14ce6, historical pre-rebase build, source
f600c365; not the rebased head aboveHardware: RTX 4090, NVIDIA 615.78.08; Core i9-13900K, 94 GiB RAM; Arch Linux 7.2.15-1-omarchy; 2026-11-13 UTC
Scene: the same Deku Tree save and Items pause page; 1920×1080 and 3840×2160 output/captures; game's own upscaler, 50% render resolution, frame generation Off, 60 cap, SDR, 240 virtual lines, no mods
Method: one reference and one candidate session at each size, windowless. The candidate selects Off, each filter and restored Off, with 1,200 further game presents per selection. Full frames and nearest-neighbour pixel crops were inspected.
RX_VK_PASS_TIMING=1supplies the GPU spans below; only complete, single-filter 600-present windows are listed.How to reproduce: load a Deku Tree save, open Items, choose the output size, Render resolution 50%, frame generation Off and SDR; change CRT filter through each choice with CRT lines at 240. For the diagnostic lines, set
RX_VK_PASS_TIMING=1and wait beyond 600 presents after each change.These are diagnostics under shared load, not performance measurements. These older sessions were not a quiet, interleaved comparison; the new Off result is reported separately above. Other GPU work was present before admission; its exact overlap with the 1080p sessions was not recorded. The 4K sessions excluded other test games but still shared heavily loaded CPUs. This is not an FPS, display-latency or generated-frame speed claim.
d725b363Diagnostic GPU span per present, downsample plus CRT; not a measurement:
Each slash lists separate complete windows, not paired repetitions. The timestamp profiler orders work and can change its cost; these numbers are not a prediction for normal presentation.
Off identity: the production-path fixture checks that Off adds no CRT work and preserves the existing scaling/cropping/HDR routes. The inspected captures show the unfiltered picture returning. They are not bit-identical independent game snapshots: exact full-frame comparison at 1080p finds differences in the separately captured world background; switching back to Off differs in the animated selection region. The new quiet Off comparison above supplies timing evidence at 1080p only. Synchronized-input pixel proof is still missing; the captures do not establish byte-for-byte equality.
Testing
Tested: the focused Linux coverage is 24 passing driver invocations: 10 compiled drivers with both GCC and Clang, plus four source/shader drivers. These cover the CRT tests, every main test that reads the hand-merged frame-generation file, and main's downscale shader check. The corrected options driver passes at
1982a687; the other positive checks passed at1dfd7d62, before that options-fixture-only amendment, and were not repeated. Both compilers also reject the old CRT scaling function and a version that puts CRT before the required downscale, four expected runtime failures in total. No assertion or warning was weakened. These are focused results, not a whole-suite total.The new cases cover CRT Off and all four filters while shrinking in either dimension, scale-before-CRT ordering and input size, checked cropping, unavailable-filter fallback, same-size/upscale behavior and Native-PQ bypass. All four CRT shader arrays match local recompilation and their original imports match the pinned hashes. Main's scale shader passes its source-hash and compilation checks; its different glslang version means its generated bytes were not compared.
The CRT options and raster-policy fixtures pass Windows-target syntax checks. The resource and scaling fixtures remain blocked by the syntax check's rule against their inherited conditional warning definitions; those definitions were not changed to make the check pass. None of the changed C++ fixtures was executed on Windows. The initial options compile failures are retained in the update history, not counted as passes.
Earlier focused checks at
2ad245bdremain historical evidence. The original imports and shader algorithms are unchanged, while the new downscale integration still needs a picture check.Linux builds at main
0c74253dand this PR2ad245bdnow have matched preparation, an inspected ordinary warm-up each, and the four valid Off-only 1080p runs under Measurements. The built source stamps arecb01b5a3/9aeb0cbf. Both preparation commands completed with one failed shader program reported; the selected measured phases logged no draw omissions or shader waits. The eight timing captures were inspected. No fatal/device/kernel faults or >1 s present gaps were observed in those four runs; B2's one virtual-core scheduler warning preceded its measured phase.The older Linux builds and four windowless game sessions below remain historical reference evidence: Off and all four filters at 1080p/4K, live changes, return to Off and no observed fatal/device/kernel faults with complete journal coverage. The old full-suite result (
513 passed, 1 failed, 20 skippedat4c673972) is not a result for this rebase. Likewise, the two older Windows-target fixture compiles are not a Windows build of this head.The same
tests/test_crt_options.pywas now pointed at main0c74253d: it fails withFAIL: missing production CRT filter rowbefore compilation, because this feature is absent. It passes on2ad245bd, whose source is unchanged by the later documentation-only carry to0b4cd574.Built for Windows and Linux at the current head, not run: At
e94d24624d11798c61ebed51e3f82cd998abc8be, the Windows x86-64 Vulkan renderer DLL and all 47 selected host translation units were cross-built on Linux with llvm-mingw 20261104 (Clang 23.1.5), using the project's production flags and pinned dependencies (renderer: 35 newly compiled / 170 reused; host: 1 newly compiled / 46 reused; lifted C: 0 newly compiled / 80 reused; all Windows units: 36 newly compiled / 610 reused from matching object-cache entries), the renderer DLL linked and the full game executable linked; nothing was run on Windows or under Wine, and this is neither the project's hosted builder run nor a Windows UTF-8 run-time path test.The exact same head
e94d24624d11798c61ebed51e3f82cd998abc8bealso compiled and linked its full native Linux executable and renderer with Clang 23.1.3 through the documented resume path (200 newly compiled units, 288 matching Linux-cache units, source stamp95758e73), using the verified target-neutral C from lift5c175a4727d82f85e425e85400dca17fea0750fbrather than carrying the patch onto another commit; neither Linux binary was run.Built for Windows and Linux at the previous head, not run: At
1982a68738c83c6f67b312c99ba5ee9ac507652a, the Windows x86-64 Vulkan renderer DLL and all 47 selected host translation units were cross-built on Linux with llvm-mingw 20261104 (Clang 23.1.5), using the project's production flags and pinned dependencies (renderer: 35 newly compiled / 170 reused; host: 1 newly compiled / 46 reused; lifted C: 0 newly compiled / 80 reused; all Windows units: 36 newly compiled / 610 reused from matching object-cache entries), the renderer DLL linked and the full game executable linked; nothing was run on Windows or under Wine, and this is neither the project's hosted builder run nor a Windows UTF-8 run-time path test. The same head also compiled and linked its full native Linux executable and renderer with Clang 23.1.3 through the documented resume path (37 newly compiled units, 451 matching Linux-cache units, source stamp5f26c88f), using the verified target-neutral C from lift76699aad3a6bdc4f75520c0f2c9d9c80d5731a5frather than carrying the patch onto another commit; neither Linux binary was run.Not tested: the carried head has not been run on either platform (it was built and linked for both, above). No picture check covers the new mandatory-downscale and CRT combination yet. The Linux game runs and measurements name earlier heads; they are not reruns of this carry. Nothing was run on Windows, including the changed C++ fixtures. Enabled-filter cost and 4K cost remain unmeasured, as does synchronized-input pixel identity. The earlier coverage gaps remain: a real window/swapchain, generated-frame output, motion/long play, other areas, ultrawide runtime, the 480/720-line choices in game, an HDR display and physical RGB/BGR/OLED subpixel layouts. No physical CRT calibration is claimed.
Needs other testers:
vk: CRT crt-lottes-aperture:log line (or aCRT filter unavailableline). If available, also note Expanded HDR / Native-PQ bypass, ultrawide or frame generation. These are optional notes on the same request, not separate requests (T-L5M7).Screenshots
All panels were inspected. Overview panels reduce complete frames; mask panels use 240×140 output-pixel crops enlarged 3× with nearest-neighbour sampling. They demonstrate the rendered patterns, not a monitor calibration.
Risk and rollback
A low virtual line count makes the game's small HUD/text coarser; non-integer ratios can show moire. These fixed upstream profiles assume particular colour/gamma and mask layouts, not a calibrated display. First use creates pipelines/images and can hitch. Enabled filters add GPU work and two reusable images; those resources stay cached after returning to Off. Set CRT filter to Off (
crt_filter=0) to restore the old output path; reverting the change also removes it.Reviewer notes
Start with
framegen_scale()/record_crt(), the checked source rectangle, the virtual-raster policy and the provenance file. The generated headers are compiled from generic shader sources, not from the game. Existing third-party folders are untouched. NoMkwVkHostorMkwVkRendererfield changed.The unused-feature cost at 1080p is now measured; enabled-filter cost at both sizes, synchronized-input Off pixel proof and the platform/output coverage named under Testing remain outstanding. The selection/default/dependency questions in #70 remain open and may change this slice. Arbitrary presets, LUT artwork, NTSC, phosphor feedback, bezels and Native-HDR CRT processing remain deliberately outside it; a wider preset runtime is still a maintainer dependency decision, not an added dependency here.
Issues
See #70
Checklist
For everyone
type(scope): description).main.Branch base:
cf605c85b; the actual measured source pair is named above.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).The combined request is T-L5M7, as named under Testing.
The quiet Off-only 1080p comparison is reported above. Enabled-filter and 4K costs are not covered; the older GPU spans remain diagnostics.
third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.mainand passing here.The earlier negative control at main
0c74253dfails withFAIL: missing production CRT filter row; its passing counterpart was2ad245bd. The current options fixture passes with GCC, Clang and Windows-target syntax at1982a687, after adapting its extraction prerequisites to current main. The new ordering cases also reject the old scaling function with both native compilers.tests/that reads changed source updated in this PR, not weakened.e94d24624d11798c61ebed51e3f82cd998abc8be(under Testing); nothing was run on Windows at this head, so UTF-8 path behavior at run time is not claimed for it.The earlier Linux game runs retain their named heads. Neither the carried head nor the changed C++ fixtures has been run on Windows, and no Windows run-time path test is claimed.
New on this forge; not opened on GitHub.
Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra [email protected]
Carried onto main
dd02c6cca(head0b4cd574b) by hand: main's rewritten README and development guide are kept and the CRT rows and sections placed into them; the fixture follows main's new output and Auto contract; nothing is added to the results table. The whole game-free suite on this head: 508 passed, 2 failed, 15 skipped. Still WIP: the cost of the filters is measured next against a build of the same main, then one test request for other hardware is opened.4c673972470b4cd574b7Updated to head
fe6bebefaon main514591a30; the whole game-free suite on it: 520 passed, 1 failed, 15 skipped.New in the description: the cost with the filter Off, measured on 13 November at the commits it names (main
0c74253d, this change 2ad245bd; two alternating runs each on a quiet PC, 1920x1080, no cap, no present gap over one second): the median of the run means is 10.648 ms on main and 10.562 ms with this change. The difference (-0.8%) is smaller than either build's repeat spread, so no penalty shows for the unused feature; that is not a speed-up claim. The cost with a filter switched on, and 4K, were not measured.The box for the game-free test is settled:
tests/test_crt_options.pyfails on main0c74253dfor the missing CRT rows and passes here. The test request is T-L5M7 (the four filters and Off on Windows, AMD, Intel or a Steam Deck). The Windows-and-Linux build box stays unticked until this code has been linked for Windows.One adaptation to today's main is in a new commit,
test(vk): the CRT options fixture stands in for detect_pad_layout: main'sset_rownow callsdetect_pad_layout()(from #266), and this pull request's fixture compilesset_rowfrom the menu's source, so it gets the same one-line stand-in main's own fixtures have. No check changed; the measured code is untouched.0b4cd574b7fe6bebefaaThis builds on both platforms. At
fe6bebefthe Windows renderer and the full game executable cross-built and linked on Linux with llvm-mingw 20261104 (Clang 23.1.5), and the native Linux executable and renderer built and linked; neither compiler rejected anything, and nothing was run from either build. With the Windows-and-Linux build box ticked on that basis, every box is done and this is marked ready for review. Still not exercised, as the description says: any run on Windows, a real window, the filters' own cost when switched on, and other GPUs (test request T-L5M7).WIP: feat(vk): add selectable CRT monitor and TV filtersto feat(vk): add selectable CRT monitor and TV filtersfe6bebefaa1982a68738Updated to head
1982a6873on maincf605c85b; the whole game-free suite on it: 544 passed, 2 failed, 16 skipped.Main gained the downscale fix in #307. It conflicts with this change at the start of
framegen_scale(). All six existing commits are kept with their authors, author dates, messages and trailers; one separate test/documentation commit covers the combined behavior and current fixture dependencies.The result keeps main's rule: a checked source rectangle larger than the destination takes the filtered scaling pass even with Window scaling filter Off and Sharpness 0. CRT then reads that destination-size result. CRT Off still keeps main's downscale; same-size output can still go straight to CRT when no window effect needs scaling. The game's HUD is filtered, Longshot overlays come afterwards, and the mask stays out of frame-generation history. Native PQ keeps its 10-bit scale path and bypasses SDR CRT.
The Intel compiler guard from #252 remains in the shared pipeline constructor. The resource fixture now exercises its guarded calls with the existing allocation failures. The CRT option fixture also keeps up with hold-to-toggle from #137 and Mono audio's restart helper from #136. No check was dropped or weakened.
The focused checks pass in 24 driver invocations: 10 compiled drivers with both GCC and Clang and four source/shader drivers. The corrected options driver ran at
1982a687; the other positive checks ran at1dfd7d62, before that fixture-only amendment. Both compilers reject the old scaling function and a version that moves CRT ahead of the mandatory downscale. Windows-target syntax passes for the options and raster-policy fixtures; the resource and scaling fixtures retain their existing warning-policy refusal. No changed fixture was executed on Windows. The known main pad-layout failure was left alone.A picture can change when the source is larger than the destination: main's filtered downscale now comes before CRT's virtual-raster reduction, even when the two window effects are disabled. That changes the input samples and can change fine detail and moire. This combination needs a picture check before publication, with CRT Off and each filter, then return to Off, plus same-size output as a control. No new picture or timing result is claimed. Real window and generated-frame output remain untested.
The carried head has not been built for Linux or Windows, so the build box is open. The earlier build, screenshots and timing results retain the heads they actually used.
Because main now shrinks an oversized picture before this stage, the carried head's picture was looked at before pushing: the same scene with the filter off, with each of the four filters and off again, at an output equal to the destination and at twice its size. In 1:1 crops the masks and scanlines sit on destination pixels at the same pitch in both cases, with no doubled pattern. The description's Output processing entry says what changed for oversized output and what was not tested (a real window, motion).
The carried head builds on both platforms. At
1982a687the Windows renderer and the full game executable cross-built and linked on Linux, and the native Linux executable and renderer built and linked; neither compiler rejected anything, and nothing was run from either build. The Windows-and-Linux build box is ticked again on that basis, so every box is done and this is ready for review again.Carried onto main
5e072fc8c; new heade94d24624. This carry is mechanical: every commit replays unchanged, with no conflicts. The whole suite on this exact head: 546 passed, 2 failed, 16 skipped; its only failures are main's owntests/test_pad_layout.py,tests/test_module_ids.pyandtests/test_framegen_resources.py. The build box is unticked until this pushed head has been built for Windows and Linux; it is ticked again then.1982a68738e94d24624dThe pushed head
e94d24624d11798c61ebed51e3f82cd998abc8beis now built and linked for Windows (cross-built with llvm-mingw 20261104, Clang 23.1.5) and for Linux (Clang 23.1.3); the details are under Testing and the build box is ticked again. Nothing was run on Windows.View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.