Class CRTBeam
The math of the beam simulator, which brings back the motion clarity of a CRT on displays at 120 Hz and above by lighting only the part of the picture the beam is drawing during each display refresh.
Inherited Members
Namespace: Scylla.TextMode.CRT
Assembly: ScyllaTextMode.CRT.dll
Syntax
public static class CRTBeam
Remarks
Based on the CRT beam simulator by Mark Rejhon (Blur Busters) and Timothy Lottes, version 1,
MIT licensed. Emission(float, float, float, float, float) mirrors CRTBeamEmit in
ScyllaCRTBeam.hlsl, and a test renders the shader and compares the two.
Over one CRT frame a pixel gives off its value times the gain, on average, but concentrated into the refreshes after the beam passes it. The picture is therefore darker by the gain and flickers at the CRT rate, which is also what makes motion sharp.
The model works in units of display refreshes. A CRT frame lasts Frames(double, double, bool) refreshes, for example 4 refreshes at 240 Hz for a 60 Hz CRT. The beam reaches a pixel at a position along that span given by TubePosition(float, CRTBeamScanDirection), and the pixel then emits its light budget, its value times the frame count times the gain, over the refreshes that follow. Emission(float, float, float, float, float) works out how much of that budget falls into the refresh that starts at the current window. All methods are pure, so they can be tested without a display.
The class is public so tests and tools can reproduce what the shader draws. Game code normally uses TextModeCRTBeam instead.
Fields
ANTI_RETENTION_DRIFT
How far an even number of refreshes per CRT frame is raised when anti-retention is on, so the sweep drifts slowly against the display and an LCD does not retain a pattern.
Declaration
public const float ANTI_RETENTION_DRIFT = 0.001
Field Value
| Type | Description |
|---|---|
| float |
HIGH_REFRESH_GAIN
Gain at and above HIGH_REFRESH_RATE.
Declaration
public const float HIGH_REFRESH_GAIN = 0.7
Field Value
| Type | Description |
|---|---|
| float |
HIGH_REFRESH_RATE
Display refresh rate in hertz from which HIGH_REFRESH_GAIN is used.
Declaration
public const float HIGH_REFRESH_RATE = 200
Field Value
| Type | Description |
|---|---|
| float |
LOW_REFRESH_GAIN
Gain below HIGH_REFRESH_RATE, where fewer refreshes share the light.
Declaration
public const float LOW_REFRESH_GAIN = 0.5
Field Value
| Type | Description |
|---|---|
| float |
MIN_FRAMES
Fewest display refreshes per CRT frame the simulator works with. Below that there is no refresh to leave dark.
Declaration
public const float MIN_FRAMES = 2
Field Value
| Type | Description |
|---|---|
| float |
UNSYNCED_RATE_FACTOR
How many times faster than the display the game may run before OutpacesDisplay(double, double) reports that vSync is not in effect.
Declaration
public const double UNSYNCED_RATE_FACTOR = 1.5
Field Value
| Type | Description |
|---|---|
| double |
WHOLE_RATIO_TOLERANCE
How far a ratio of display rate to CRT rate may be from a whole number and still count as that number, relative to the number (0.2 percent). It lets 119.88 Hz count as twice 60 Hz and is the tolerance for the anti-retention check as well.
Declaration
public const double WHOLE_RATIO_TOLERANCE = 0.002
Field Value
| Type | Description |
|---|---|
| double |
Methods
DefaultGain(double)
Returns the default gain for a display's refresh rate: less light per refresh on slower displays, where each lit refresh lasts longer.
Declaration
public static float DefaultGain(double displayRate)
Parameters
| Type | Name | Description |
|---|---|---|
| double | displayRate | The display's refresh rate in hertz. |
Returns
| Type | Description |
|---|---|
| float | LOW_REFRESH_GAIN below HIGH_REFRESH_RATE, otherwise HIGH_REFRESH_GAIN. |
Emission(float, float, float, float, float)
Returns the light one channel of a pixel emits during one display refresh.
Declaration
public static float Emission(float value, float tubePosition, float frames, float window, float gain)
Parameters
| Type | Name | Description |
|---|---|---|
| float | value | The channel's linear value. |
| float | tubePosition | Where the beam reaches the pixel, from TubePosition(float, CRTBeamScanDirection). |
| float | frames | Display refreshes per CRT frame, from Frames(double, double, bool). |
| float | window | Where the refresh starts within the CRT frame, in refreshes. |
| float | gain | The share of the value emitted on average. |
Returns
| Type | Description |
|---|---|
| float | The emitted light, from 0 to 1. A value times the gain above 1 is clamped, so no refresh exceeds 1. |
Remarks
The pixel's light budget is its value times frames times
gain, capped at one CRT frame. It starts when the beam reaches the
pixel and repeats every CRT frame, so the budget overlaps the refresh at the window
once from the previous frame, once from this one and once from the next. Each refresh
emits at most 1.
Frames(double, double, bool)
Returns the number of display refreshes per CRT frame, or 0 when the simulator cannot run at these rates.
Declaration
public static float Frames(double displayRate, double crtRate, bool antiRetention)
Parameters
| Type | Name | Description |
|---|---|---|
| double | displayRate | The display's refresh rate in hertz. |
| double | crtRate | The simulated CRT's refresh rate in hertz, such as 60. |
| bool | antiRetention | Whether a whole even number is raised by ANTI_RETENTION_DRIFT, which keeps an LCD from retaining a pattern. Not needed on OLED displays. |
Returns
| Type | Description |
|---|---|
| float | Refreshes per CRT frame, at least MIN_FRAMES, or 0. |
Remarks
A ratio within WHOLE_RATIO_TOLERANCE of a whole number counts as that number, so 119.88 Hz over 60 Hz gives 2. Anti-retention raises an even whole number by ANTI_RETENTION_DRIFT, so 240 Hz over 60 Hz gives 4.001. The result is 0 when either rate is 0 or less, or when the ratio is below MIN_FRAMES.
OutpacesDisplay(double, double)
Returns whether the game renders frames so much faster than the display refreshes that vSync cannot be in effect.
Declaration
public static bool OutpacesDisplay(double measuredRate, double displayRate)
Parameters
| Type | Name | Description |
|---|---|---|
| double | measuredRate | The frame rate the game reaches, in frames per second. |
| double | displayRate | The display's refresh rate in hertz. |
Returns
| Type | Description |
|---|---|
| bool | True if the game outpaces the display. |
Remarks
The simulator counts at least one refresh per frame. Without vSync every short frame
moves the beam a whole refresh on, so the beam sweeps several times too fast, and each
real refresh shows whatever phase the last frame drew. The picture then flickers
unevenly instead of at the CRT rate. The result is true when
measuredRate is more than UNSYNCED_RATE_FACTOR times
displayRate, and false when either rate is 0 or less.
ShaderVector(float, float, float, CRTBeamScanDirection)
Returns the shader vector for the composite's beam stage. The components are refreshes per CRT frame, the window, the gain and the scan direction as a number.
Declaration
public static Vector4 ShaderVector(float frames, float window, float gain, CRTBeamScanDirection direction)
Parameters
| Type | Name | Description |
|---|---|---|
| float | frames | Display refreshes per CRT frame; 0 switches the stage off. |
| float | window | Where the refresh starts within the CRT frame, in refreshes. |
| float | gain | The share of the value emitted on average. |
| CRTBeamScanDirection | direction | The scan direction. |
Returns
| Type | Description |
|---|---|
| Vector4 | The vector for |
SourceDue(long, ref long, double, out float)
Decides whether a CRT picture needs a new source frame. While the simulator runs, the source is captured once per CRT frame and held for the refreshes in between, so the picture does not change halfway through the sweep.
Declaration
public static bool SourceDue(long cycleIndex, ref long lastCycle, double crtRate, out float elapsed)
Parameters
| Type | Name | Description |
|---|---|---|
| long | cycleIndex | The CRT frame showing now, from CycleIndex. |
| long | lastCycle | The CRT frame the picture last captured, updated when a new one is due. Set it to -1 to force a capture, for example after the picture's textures were recreated. |
| double | crtRate | The simulated CRT's refresh rate in hertz. |
| float | elapsed | The time the source's afterglow and signal advance by: one CRT period per CRT frame since the last capture, or one period for a forced capture. 0 when no capture is due. |
Returns
| Type | Description |
|---|---|
| bool | True if the source must be captured this refresh. |
TubePosition(float, CRTBeamScanDirection)
Returns where the beam reaches a raster position during the CRT frame: 0 at the start of the sweep and 1 at its end.
Declaration
public static float TubePosition(float rasterY, CRTBeamScanDirection direction)
Parameters
| Type | Name | Description |
|---|---|---|
| float | rasterY | Raster position from -1 at the bottom of the tube to 1 at the top. |
| CRTBeamScanDirection | direction | The scan direction. |
Returns
| Type | Description |
|---|---|
| float | The position along the sweep from 0 to 1. It is 0 everywhere for Global, so the whole tube lights at once. |