Idea: a choice of CRT filters, from clean video monitors to consumer TVs #70

Open
opened 2026-11-13 01:34:01 +00:00 by mooki · 0 comments

The idea

Add CRT filter to Graphics: a choice of convincing beam, scanline and phosphor-mask looks, using the work already made by the RetroArch/libretro shader community. Off stays the default, with the existing picture and no extra GPU pass or image allocation.

Start with native Vulkan adaptations of three small, explicitly licensed shaders, offering four looks:

Choice Look Upstream passes Licence / author and source
Lottes — aperture grille Flat, clean RGB grille; professional-monitor-style rather than a claim to reproduce a particular Sony PVM/BVM 1 Public domain; Timothy Lottes, aperture-mask setting, curvature off
Lottes — consumer TV Curved glass, bright beam/bloom and compressed TV shadow mask 1 Same public-domain source, TV-mask setting
Hyllian — sharp Cubic reconstruction, strong scanlines and alternating magenta/green mask 1 MIT; Hyllian, cgwg mask, DariusG/hunterk ports
zfast — light Softer scanlines and a fine monochrome aperture mask; inexpensive alternative 1 GPL-2.0-or-later; Greg Hogan / SoltanGris42

Proposed keys: crt_filter=0..4 in that order, and CRT lines / crt_lines=0..2 for 240 / 480 / 720 virtual lines (240 initially). The remake renders well above 240p: this deliberately samples its finished picture onto a virtual CRT raster, independent of Render resolution. Cap that raster to half the output height so scanlines have at least two output pixels. Preserve aspect; do not force 4:3 or add integer scaling. Exact integer multiples give the most regular lines.

Placement: after the game's upscaling and the window scaling/sharpness/ultrawide-side treatment, before Longshot's Insert menu and FPS overlay. The game's own HUD is part of the filtered picture. Apply it to each displayed frame after frame generation, so optical flow never tracks the mask. Masks use destination pixels; wide gameplay fills the existing wide rectangle, and centred 16:9 menus keep their current sides/bars.

These shaders expect SDR, not PQ. Expanded HDR can expand the filtered SDR through the existing output pass. Native HDR bypasses the filter, with the saved choice kept and the reason shown, until proper linear-light/HDR presets are validated. No silent PQ-as-sRGB processing.

The first slice adds a virtual-raster downsample and one CRT pass, with reused images. Lottes is the heavier choice; Hyllian and zfast are cheaper by inspection, not yet measured. The PR should show Off against main, full frames and mask close-ups at ordinary and 4K output, and the GPU's own counters without pretending a shared-load run is a quiet benchmark.

Anything else

Recommendation: ship the small native set first. Keep original files and notices unchanged in a new third-party folder, record their commit and hashes, and generate SPIR-V with the existing shader build method. No new runtime dependency is needed.

A custom partial Slang loader looks small but soon needs preset inheritance, aliases, colour-space targets, LUTs, history and feedback. Prefer evaluating an existing preset runtime for a wider selection rather than maintaining an incomplete second one.

Outside this first slice: arbitrary downloaded presets; NTSC/composite signal emulation; persistent phosphor/feedback; bezels/reflections; individually calibrated Sony models; Native HDR; a full shader-parameter editor. Those are useful extensions, not features the first filters should pretend to have.

Survey: the larger collection, licensing gates and runtime choices

All shader links below are pinned to libretro/slang-shaders 2345ee91. Counts are the representative preset graphs, not GPU timings; floating-point targets alone do not mean HDR.

Family Look and representative passes Extra inputs / cost by inspection Licence gate
Royale / Kurozumi / Royale-fast PVM Detailed beam, convergence, glow and grille/slot/shadow masks: 12 / 13 / 8 passes Six mask textures for Royale; Kurozumi adds grading/LUTs; PVM-fast has three masks. High; reduced-size blur stages help. SDR TroggleMonkey's GPL-2.0-or-later code. Kurozumi and Hyllian's PVM-fast settings and PNG licences still need separate clearance
Guest Advanced Broad consumer/pro-monitor tuning; 12 passes, fast 9, fastest 5, NTSC 19 Four LUTs, afterglow and average-luminance feedback, mipmaps; high. SDR guest(r)/Dr. Venom GPL-2.0-or-later; LUT/stock-pass clearance separate
Geom Curved tube, tilt, corners and beam; 1 pass Frame count for interlace parity, no history texture; medium. SDR cgwg/Themaister/DOLLS, GPL-2.0-or-later
EasyMode / Aperture Flat tube / clean grille with halation; 1 pass each Source only, no LUT/history; low–medium / medium. SDR EasyMode's EasyMode and Aperture say only “License: GPL”, without a version; first import avoids that ambiguity
Full Hyllian CRT, glow, mask and geometry; current preset 5 passes, unlike the 1-pass Fast above Two Sony Wega LUTs, previous-pass alias and mipmaps; medium–high. SDR Hyllian's MIT base; whole preset's helpers/LUTs not yet cleared
Sony Megatron v2 Display-pixel phosphor simulation, PVM presets; 2 passes (NTSC 8) Procedural masks; HDR10/scRGB/SDR output and brightness/subpixel configuration; low graph overhead Major Pain The Cactus; standalone source has no complete compatible grant located. Do not import until clarified
Mega Bezel CRT plus cabinet, reflection and artwork; Potato 14, Standard 30, Advanced 42, Smooth-Advanced 48 passes Many LUT/artwork bindings and feedback surfaces; very high complexity; normal Guest roots SDR, separate HDR chains HyperspaceMadness's GPL-3.0-or-later source; each dependency still needs checking. Duimon packs are CC-BY-NC-ND-4.0, not a compatible bulk import
GTU / NTSC signal stages RGB/S-video/composite bandwidth and colour bleed before the tube; GTU 3, Tiny NTSC 1, NTSC Simple 2 passes Source and frame count; GTU float intermediates; no sampled history. SDR signal assumptions; medium, source-width-dependent aliaspider's GTU GPLv3, DariusG's Tiny NTSC GPL-2.0-or-later. Not all familiar NTSC ports have clear grants: the blargg port explicitly says “no license given”

Existing runtime: librashader 0.12.1 implements Slang presets with a Vulkan backend and first-class C API. Its implementation is MPL-2.0 OR GPL-3.0-only; C headers are MIT. This offers a compatible licence route, not clearance for shaders loaded through it.

It needs stable Rust 1.88 for the non-WGPU build, Cargo and a native compiler stack; the compiler/reflection/image dependencies remain even with only Vulkan selected. The two native compiler source crates alone are about 6.2 MB compressed. A previous all-backend Windows archive was about 28 MB; a Vulkan-only linked footprint has not been measured. This is materially larger than three precompiled native shaders. The host must handle image layouts, queue/frames-in-flight synchronization and output colour space; loading a preset does not make SDR shaders HDR-correct. Build and integration contract.

There are real precedents: ares uses librashader, DuckStation adapts Lottes natively, and the native zelda3 port has its own GLSL preset loader. These demonstrate the three approaches, not permission to copy unrelated port code.

Questions for maintainers:

  1. Is this small default-off native set a good starting point, and is 240 the right initial virtual line count for this remake?
  2. For the larger multipass collection, would a pinned Rust-built library with a C/Vulkan interface be acceptable after dependency, licence and packaging review, or should Longshot remain native-port-only?
  3. Should a later HDR slice aim for display-calibrated, HDR-first presets rather than expanding SDR CRT output?

Claimed for an initial WIP implementation and Linux/NVIDIA checks. Windows, AMD/Intel and HDR-display results will remain explicit gaps until tested.

### The idea Add **CRT filter** to Graphics: a choice of convincing beam, scanline and phosphor-mask looks, using the work already made by the RetroArch/libretro shader community. **Off stays the default**, with the existing picture and no extra GPU pass or image allocation. Start with native Vulkan adaptations of three small, explicitly licensed shaders, offering four looks: | Choice | Look | Upstream passes | Licence / author and source | |---|---|---:|---| | Lottes — aperture grille | Flat, clean RGB grille; professional-monitor-style rather than a claim to reproduce a particular Sony PVM/BVM | 1 | Public domain; [Timothy Lottes](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/crt/shaders/crt-lottes.slang), aperture-mask setting, curvature off | | Lottes — consumer TV | Curved glass, bright beam/bloom and compressed TV shadow mask | 1 | Same [public-domain source](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/crt/shaders/crt-lottes.slang), TV-mask setting | | Hyllian — sharp | Cubic reconstruction, strong scanlines and alternating magenta/green mask | 1 | MIT; [Hyllian, cgwg mask, DariusG/hunterk ports](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/crt/shaders/hyllian/crt-hyllian-fast.slang) | | zfast — light | Softer scanlines and a fine monochrome aperture mask; inexpensive alternative | 1 | GPL-2.0-or-later; [Greg Hogan / SoltanGris42](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/crt/shaders/zfast_crt/zfast_crt_impl.inc) | Proposed keys: **`crt_filter=0..4`** in that order, and **CRT lines / `crt_lines=0..2`** for **240 / 480 / 720 virtual lines** (240 initially). The remake renders well above 240p: this deliberately samples its finished picture onto a virtual CRT raster, independent of Render resolution. Cap that raster to half the output height so scanlines have at least two output pixels. Preserve aspect; do not force 4:3 or add integer scaling. Exact integer multiples give the most regular lines. **Placement:** after the game's upscaling and the window scaling/sharpness/ultrawide-side treatment, before Longshot's Insert menu and FPS overlay. The game's own HUD is part of the filtered picture. Apply it to each displayed frame after frame generation, so optical flow never tracks the mask. Masks use destination pixels; wide gameplay fills the existing wide rectangle, and centred 16:9 menus keep their current sides/bars. These shaders expect **SDR**, not PQ. Expanded HDR can expand the filtered SDR through the existing output pass. **Native HDR bypasses the filter**, with the saved choice kept and the reason shown, until proper linear-light/HDR presets are validated. No silent PQ-as-sRGB processing. The first slice adds a virtual-raster downsample and one CRT pass, with reused images. Lottes is the heavier choice; Hyllian and zfast are cheaper by inspection, not yet measured. The PR should show Off against main, full frames and mask close-ups at ordinary and 4K output, and the GPU's own counters without pretending a shared-load run is a quiet benchmark. ### Anything else **Recommendation:** ship the small native set first. Keep original files and notices unchanged in a new third-party folder, record their commit and hashes, and generate SPIR-V with the existing shader build method. No new runtime dependency is needed. A custom partial Slang loader looks small but soon needs preset inheritance, aliases, colour-space targets, LUTs, history and feedback. Prefer evaluating an existing preset runtime for a wider selection rather than maintaining an incomplete second one. **Outside this first slice:** arbitrary downloaded presets; NTSC/composite signal emulation; persistent phosphor/feedback; bezels/reflections; individually calibrated Sony models; Native HDR; a full shader-parameter editor. Those are useful extensions, not features the first filters should pretend to have. <details> <summary>Survey: the larger collection, licensing gates and runtime choices</summary> All shader links below are pinned to libretro/slang-shaders `2345ee91`. Counts are the representative preset graphs, not GPU timings; floating-point targets alone do not mean HDR. | Family | Look and representative passes | Extra inputs / cost by inspection | Licence gate | |---|---|---|---| | Royale / Kurozumi / Royale-fast PVM | Detailed beam, convergence, glow and grille/slot/shadow masks: 12 / 13 / 8 passes | Six mask textures for Royale; Kurozumi adds grading/LUTs; PVM-fast has three masks. High; reduced-size blur stages help. SDR | TroggleMonkey's [GPL-2.0-or-later code](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/crt/shaders/crt-royale/src/crt-royale-first-pass-linearize-crt-gamma-bob-fields.slang). [Kurozumi](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/presets/crt-royale-kurozumi.slangp) and [Hyllian's PVM-fast](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/presets/crt-royale-fast/crt-royale-pvm-rgb.slangp) settings and PNG licences still need separate clearance | | Guest Advanced | Broad consumer/pro-monitor tuning; 12 passes, fast 9, fastest 5, NTSC 19 | Four LUTs, afterglow and average-luminance feedback, mipmaps; high. SDR | guest(r)/Dr. Venom [GPL-2.0-or-later](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/crt/shaders/guest/advanced/pre-shaders-afterglow.slang); LUT/stock-pass clearance separate | | Geom | Curved tube, tilt, corners and beam; 1 pass | Frame count for interlace parity, no history texture; medium. SDR | cgwg/Themaister/DOLLS, [GPL-2.0-or-later](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/crt/shaders/crt-geom.slang) | | EasyMode / Aperture | Flat tube / clean grille with halation; 1 pass each | Source only, no LUT/history; low–medium / medium. SDR | EasyMode's [EasyMode](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/crt/shaders/crt-easymode.slang) and [Aperture](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/crt/shaders/crt-aperture.slang) say only “License: GPL”, without a version; first import avoids that ambiguity | | Full Hyllian | CRT, glow, mask and geometry; current preset 5 passes, unlike the 1-pass Fast above | Two Sony Wega LUTs, previous-pass alias and mipmaps; medium–high. SDR | Hyllian's [MIT base](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/crt/shaders/hyllian/crt-hyllian-base.slang); whole preset's helpers/LUTs not yet cleared | | Sony Megatron v2 | Display-pixel phosphor simulation, PVM presets; 2 passes (NTSC 8) | Procedural masks; HDR10/scRGB/SDR output and brightness/subpixel configuration; low graph overhead | Major Pain The Cactus; [standalone source](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/hdr/shaders/include/sony_megatron_v2.h) has no complete compatible grant located. Do not import until clarified | | Mega Bezel | CRT plus cabinet, reflection and artwork; Potato 14, Standard 30, Advanced 42, Smooth-Advanced 48 passes | Many LUT/artwork bindings and feedback surfaces; very high complexity; normal Guest roots SDR, separate HDR chains | HyperspaceMadness's [GPL-3.0-or-later source](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/bezel/Mega_Bezel/shaders/base/reflection.inc); each dependency still needs checking. [Duimon packs are CC-BY-NC-ND-4.0](https://github.com/Duimon/Duimon-Mega-Bezel/blob/d03dabf6e6b190dbf9b692efd492d8edd21abbb9/LICENSE.md), not a compatible bulk import | | GTU / NTSC signal stages | RGB/S-video/composite bandwidth and colour bleed before the tube; GTU 3, Tiny NTSC 1, NTSC Simple 2 passes | Source and frame count; GTU float intermediates; no sampled history. SDR signal assumptions; medium, source-width-dependent | aliaspider's [GTU GPLv3](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/crt/shaders/gtu-v050/pass1.slang), DariusG's [Tiny NTSC GPL-2.0-or-later](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/ntsc/shaders/ntsc-simple/tiny_ntsc.slang). Not all familiar NTSC ports have clear grants: [the blargg port explicitly says “no license given”](https://github.com/libretro/slang-shaders/blob/2345ee91602a7404d3a1a6a5df6108d7305fdad4/ntsc/shaders/blargg/blargg-0.slang) | **Existing runtime:** [librashader 0.12.1](https://github.com/SnowflakePowered/librashader/tree/07ac790cdb9826f37cf8e1daf5011f03e3c4daa7) implements Slang presets with a Vulkan backend and first-class C API. Its implementation is [MPL-2.0 OR GPL-3.0-only](https://github.com/SnowflakePowered/librashader/blob/07ac790cdb9826f37cf8e1daf5011f03e3c4daa7/librashader-capi/Cargo.toml); [C headers are MIT](https://github.com/SnowflakePowered/librashader/blob/07ac790cdb9826f37cf8e1daf5011f03e3c4daa7/include/librashader.h). This offers a compatible licence route, not clearance for shaders loaded through it. It needs stable Rust 1.88 for the non-WGPU build, Cargo and a native compiler stack; the compiler/reflection/image dependencies remain even with only Vulkan selected. The two native compiler source crates alone are about 6.2 MB compressed. A previous all-backend Windows archive was about 28 MB; **a Vulkan-only linked footprint has not been measured**. This is materially larger than three precompiled native shaders. The host must handle image layouts, queue/frames-in-flight synchronization and output colour space; loading a preset does not make SDR shaders HDR-correct. [Build and integration contract](https://github.com/SnowflakePowered/librashader/blob/07ac790cdb9826f37cf8e1daf5011f03e3c4daa7/README.md). There are real precedents: [ares uses librashader](https://github.com/ares-emulator/ares/blob/0b928930af4cc59ed99e942996f0d3c815379388/ruby/video/opengl/main.hpp), [DuckStation adapts Lottes natively](https://github.com/stenzek/duckstation/blob/b0ee1ff8f17b9d469fe6553931082926db636294/data/resources/shaders/crt-lottes.glsl), and [the native zelda3 port has its own GLSL preset loader](https://github.com/snesrev/zelda3/blob/45a149d8b4bb8098f53d1009f76e76e7d8b79bbf/src/glsl_shader.c). These demonstrate the three approaches, not permission to copy unrelated port code. </details> **Questions for maintainers:** 1. Is this small default-off native set a good starting point, and is 240 the right initial virtual line count for this remake? 2. For the larger multipass collection, would a pinned Rust-built library with a C/Vulkan interface be acceptable after dependency, licence and packaging review, or should Longshot remain native-port-only? 3. Should a later HDR slice aim for display-calibrated, HDR-first presets rather than expanding SDR CRT output? Claimed for an initial WIP implementation and Linux/NVIDIA checks. Windows, AMD/Intel and HDR-display results will remain explicit gaps until tested.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot#70
No description provided.