Class CRTGeometry
The CPU side of the CRT math: the tube warp, the mask period, the fitting of the raster into the screen, the phosphor decay factors, the white point and the video signal's subcarrier and filters.
Inherited Members
Namespace: Scylla.TextMode.CRT
Assembly: ScyllaTextMode.CRT.dll
Syntax
public static class CRTGeometry
Remarks
Warp(Vector2, Vector2, CRTCurvatureShape, float) uses the same formula as CRTWarp in ScyllaCRT.hlsl. The
pointer mapping relies on the two agreeing, so a change to one must be made to the other.
All methods are static and allocate nothing, so they are safe to call every frame. None of them needs a graphics device, so the math can be checked in plain tests.
Methods
BezelPixels(float, Vector2)
Returns the width of the bezel in whole output pixels: the same on every side, measured from the height of the area, and never more than a quarter of the area's shorter side.
Declaration
public static float BezelPixels(float bezelWidth, Vector2 areaPixels)
Parameters
| Type | Name | Description |
|---|---|---|
| float | bezelWidth | Bezel width as a fraction of the area height, from the settings. |
| Vector2 | areaPixels | Size of the area the tube and its bezel share, in output pixels. |
Returns
| Type | Description |
|---|---|
| float | The bezel width in pixels; 0 when there is no bezel. |
ChromaFilterSigma(float, CRTSignalStandard, float)
Computes the width of the filter that separates the color from a composite signal.
Declaration
public static float ChromaFilterSigma(float bandwidthMHz, CRTSignalStandard standard, float cyclesPerPixel)
Parameters
| Type | Name | Description |
|---|---|---|
| float | bandwidthMHz | The chroma bandwidth in MHz. |
| CRTSignalStandard | standard | The broadcast standard. |
| float | cyclesPerPixel | Subcarrier cycles per raster pixel. |
Returns
| Type | Description |
|---|---|
| float | The filter's standard deviation in raster pixels. |
Remarks
Demodulating leaves a copy of the color at twice the subcarrier frequency, which the filter has to remove. It is therefore never narrower than 0.3 of a subcarrier cycle, whatever the bandwidth.
DecayFactor(float, float)
Computes how much a phosphor fades over one frame.
Declaration
public static float DecayFactor(float deltaTime, float timeConstantMilliseconds)
Parameters
| Type | Name | Description |
|---|---|---|
| float | deltaTime | Elapsed time in seconds, already clamped by the caller. |
| float | timeConstantMilliseconds | Decay time constant in milliseconds. |
Returns
| Type | Description |
|---|---|
| float | The factor |
Remarks
The factor depends only on elapsed time, so the same trail appears at any frame rate: two frames of 8 ms fade exactly as much as one frame of 16 ms.
EffectiveCurvature(Vector2, CRTCurvatureShape, bool)
Returns the curvature the shader applies for a faceplate shape.
Declaration
public static Vector2 EffectiveCurvature(Vector2 curvature, CRTCurvatureShape shape, bool enabled)
Parameters
| Type | Name | Description |
|---|---|---|
| Vector2 | curvature | Barrel strength per axis. |
| CRTCurvatureShape | shape | Faceplate shape. |
| bool | enabled | Whether curvature is on at all. |
Returns
| Type | Description |
|---|---|
| Vector2 | The curvature per axis, with the axes the shape keeps flat set to zero, or zero when curvature is off. |
FieldPhase(long, float)
Returns the subcarrier phase of a field, which moves the dots of dot crawl from one field to the next.
Declaration
public static float FieldPhase(long field, float fieldShift)
Parameters
| Type | Name | Description |
|---|---|---|
| long | field | Number of the field since the signal started. |
| float | fieldShift | Phase shift per field, in cycles. |
Returns
| Type | Description |
|---|---|
| float | The phase in cycles, from 0 up to but not including 1. |
FieldRate(CRTSignalStandard)
Returns the number of fields a broadcast standard shows per second.
Declaration
public static float FieldRate(CRTSignalStandard standard)
Parameters
| Type | Name | Description |
|---|---|---|
| CRTSignalStandard | standard | The standard. |
Returns
| Type | Description |
|---|---|
| float | 59.94 for NTSC, 50 for PAL. |
FitTube(Vector2, float, CRTScaleMode, int)
Computes where the tube sits inside a screen area, in output pixels.
Declaration
public static Rect FitTube(Vector2 areaPixels, float aspect, CRTScaleMode mode, int lineCount)
Parameters
| Type | Name | Description |
|---|---|---|
| Vector2 | areaPixels | Size of the available area in output pixels. |
| float | aspect | Width of the picture divided by its height. |
| CRTScaleMode | mode | How the picture is fitted. |
| int | lineCount | Number of scanlines, used by Integer mode. 0 treats Integer like Fit. |
Returns
| Type | Description |
|---|---|
| Rect | The tube rectangle relative to the bottom-left corner of the area, with its position rounded to whole pixels. Integer mode shrinks the height to a whole number of pixels per scanline, and falls back to Fit when not even one pixel per scanline fits. |
ImageRaster(Vector2Int, int, int, out Vector2Int, out int)
Computes the raster for an image effect that covers a whole target, such as a camera.
Declaration
public static bool ImageRaster(Vector2Int outputSize, int pitch, int sourceLines, out Vector2Int captureSize, out int lineCount)
Parameters
| Type | Name | Description |
|---|---|---|
| Vector2Int | outputSize | Size of the target in pixels. |
| int | pitch | Preferred height of one scanline in output pixels. |
| int | sourceLines | A fixed number of scanlines for a low-resolution look, such as 240, or 0 to derive the
count from |
| Vector2Int | captureSize | Size the image is copied at before the raster pass: the full target, or with a fixed line count, one pixel per scanline at the target's aspect ratio. |
| int | lineCount | The number of scanlines. |
Returns
| Type | Description |
|---|---|
| bool | False when the target has no area. |
MaskPeriod(float, int, int)
Computes the width of one mask triad in output pixels.
Declaration
public static int MaskPeriod(float outputHeightPixels, int tvl, int overrideValue)
Parameters
| Type | Name | Description |
|---|---|---|
| float | outputHeightPixels | Height of the screen in output pixels. The screen rather than the tube, so a CRT covering part of the screen gets the same triad size as one covering all of it. |
| int | tvl | Mask density in triads per screen height. |
| int | overrideValue | A fixed period, or 0 to derive it from |
Returns
| Type | Description |
|---|---|
| int | One of 2, 3, 4 or 6. Only these periods divide into three colors, or three colors and a gap, without leaving one color brighter than the others. |
Remarks
The period counts fine mask pixels, which are 1.5 times finer than screen pixels in views and camera effects. A period of 6 is therefore a 4-pixel triad on screen.
MaskPeriodRaw(float, int, int)
Returns the width of one mask triad in output pixels as the settings ask for it, before any rounding. The texture mask uses it as it is.
Declaration
public static float MaskPeriodRaw(float outputHeightPixels, int tvl, int overrideValue)
Parameters
| Type | Name | Description |
|---|---|---|
| float | outputHeightPixels | Height of the screen in output pixels. |
| int | tvl | Mask density in triads per screen height. |
| int | overrideValue | A fixed period, or 0 to derive it from |
Returns
| Type | Description |
|---|---|
| float | The period in output pixels. |
Remarks
The period counts fine mask pixels, which are 1.5 times finer than screen pixels in views and camera effects. A period of 6 is therefore a 4-pixel triad on screen.
MaskSubpixelGain(int)
Returns how much brighter one lit subpixel must be so that a mask of this period lets through the same average light per channel as no mask at all.
Declaration
public static float MaskSubpixelGain(int period)
Parameters
| Type | Name | Description |
|---|---|---|
| int | period | Mask period from MaskPeriod(float, int, int). |
Returns
| Type | Description |
|---|---|
| float | 2 for period 2, 3 for period 3, 4 for period 4 and 3 for period 6. |
Remarks
Period 2 alternates magenta and green, so each channel is lit on one pixel of two. Period 4 is red, green, blue and a dark gap. Period 6 doubles each color of period 3.
ScanlinePitchPixels(float, int, bool, bool)
Computes the height of one scanline in whole screen pixels.
Declaration
public static int ScanlinePitchPixels(float rowHeightPixels, int pitch, bool isCurved = false, bool isExact = false)
Parameters
| Type | Name | Description |
|---|---|---|
| float | rowHeightPixels | Height of one character row on screen, in pixels. |
| int | pitch | Preferred height of one scanline, in screen pixels. |
| bool | isCurved | Whether the tube is curved. A curved tube keeps taller scanlines, down to MIN_CURVED_SCANLINES_PER_ROW per row, because a dense pitch beats against the pixel rows as moire rings once the warp makes it fractional. |
| bool | isExact | Whether the pitch was chosen on purpose (ExactScanlinePitch). It is then kept as set, only raised to the minimum pitch. |
Returns
| Type | Description |
|---|---|
| int | The preferred pitch, lowered when a character row would otherwise get fewer than MIN_AUTO_SCANLINES_PER_ROW scanlines (or MIN_CURVED_SCANLINES_PER_ROW on a curved tube), because fewer lines than that make letters hard to read. Never below MIN_SCANLINE_PITCH. |
ScanlinesPerRow(float, int)
Computes how many scanlines a character row gets when a fixed count per row is set.
Declaration
public static int ScanlinesPerRow(float rowHeightPixels, int scanlinesPerRow)
Parameters
| Type | Name | Description |
|---|---|---|
| float | rowHeightPixels | Height of one character row on screen, in pixels. |
| int | scanlinesPerRow | The count per row the settings ask for, above 0. |
Returns
| Type | Description |
|---|---|
| int | The count per row, at least 1. |
Remarks
A scanline shorter than MIN_SCANLINE_PITCH pixels is finer than the screen can show: the lines beat against the pixel rows. The count is therefore lowered to what fits at that pitch, the same limit a scanline height obeys.
SignalFilterSigma(float, CRTSignalStandard, float)
Computes the width of the Gaussian low-pass filter that limits a signal to a bandwidth.
Declaration
public static float SignalFilterSigma(float bandwidthMHz, CRTSignalStandard standard, float cyclesPerPixel)
Parameters
| Type | Name | Description |
|---|---|---|
| float | bandwidthMHz | The bandwidth in MHz, where the filter lets through 71 percent of the amplitude. |
| CRTSignalStandard | standard | The broadcast standard, whose subcarrier sets the scale of a MHz on the raster. |
| float | cyclesPerPixel | Subcarrier cycles per raster pixel, from SubcarrierCyclesPerPixel(int, CRTSignalStandard, float). |
Returns
| Type | Description |
|---|---|
| float | The filter's standard deviation in raster pixels. |
Remarks
The bandwidth is measured against the subcarrier: 1.3 MHz of chroma on NTSC is 0.36
of a subcarrier cycle per cycle. A Gaussian with standard deviation sigma passes
exp(-2 pi^2 sigma^2 f^2) of the amplitude at frequency f, which is 1 over the
square root of 2 at sigma = 0.1325 / f.
SlotMaskCompensation(CRTMaskType, float, float, int)
Computes the gain that makes up for the dark rows a slot mask leaves between its slots, so a slot mask keeps the average brightness of the other masks.
Declaration
public static float SlotMaskCompensation(CRTMaskType maskType, float slotGap, float maskStrength, int slotHeight)
Parameters
| Type | Name | Description |
|---|---|---|
| CRTMaskType | maskType | The mask type. Only a slot mask has a compensation. |
| float | slotGap | Darkness of the gap between slots, from 0 to 1. |
| float | maskStrength | Strength of the mask, from 0 to 1. |
| int | slotHeight | Height of one slot in mask cells. |
Returns
| Type | Description |
|---|---|
| float | The gain, 1 for every mask other than a slot mask. |
SnapTube(Rect, Vector2Int)
Shrinks a tube rectangle to an exact pixel size, keeping it centered and on whole pixels.
Declaration
public static Rect SnapTube(Rect tube, Vector2Int size)
Parameters
| Type | Name | Description |
|---|---|---|
| Rect | tube | The fitted tube rectangle, in output pixels. |
| Vector2Int | size | The exact size the tube should have. |
Returns
| Type | Description |
|---|---|
| Rect | The snapped rectangle. |
SnappedScanlinePitch(float, int)
Rounds the scanline height that a fixed count per row gives to a whole number of pixels, when it is close to one.
Declaration
public static int SnappedScanlinePitch(float rowHeightPixels, int scanlinesPerRow)
Parameters
| Type | Name | Description |
|---|---|---|
| float | rowHeightPixels | Height of one character row on screen, in pixels. |
| int | scanlinesPerRow | The count per row, from ScanlinesPerRow(float, int). |
Returns
| Type | Description |
|---|---|
| int | The whole pitch in pixels, never below the minimum pitch, or 0 when the fractional pitch is kept. |
Remarks
A fixed count per row lines the scanlines up with the text, but makes them a fractional number of pixels tall, and a fractional pitch beats against the pixel rows as uneven bands. Below SCANLINE_ALIGN_MIN_PITCH pixels the bands are plain to see at any fraction, such as 2.17, and the alignment with the text is too fine to notice, so the pitch is always rounded. From that pitch up it is rounded only within SCANLINE_SNAP_TOLERANCE of a whole number. A rounded pitch gives every scanline the same height, and the lines drift by a fraction of a pixel against the text rows.
SubcarrierCyclesPerPixel(int, CRTSignalStandard, float)
Computes how many color subcarrier cycles fall on one raster pixel.
Declaration
public static float SubcarrierCyclesPerPixel(int rasterWidth, CRTSignalStandard standard, float period)
Parameters
| Type | Name | Description |
|---|---|---|
| int | rasterWidth | Width of the raster in pixels. |
| CRTSignalStandard | standard | The broadcast standard. |
| float | period | Length of one cycle in raster pixels, or 0 to spread the standard's visible cycles across the raster width. |
Returns
| Type | Description |
|---|---|
| float | Cycles per pixel, or 0 when the raster has no width. |
SubcarrierMHz(CRTSignalStandard)
Returns the color subcarrier frequency of a broadcast standard.
Declaration
public static float SubcarrierMHz(CRTSignalStandard standard)
Parameters
| Type | Name | Description |
|---|---|---|
| CRTSignalStandard | standard | The standard. |
Returns
| Type | Description |
|---|---|
| float | The subcarrier frequency in MHz. |
SubcarrierPhase(float, float, float, float, float)
Returns the subcarrier phase at a raster pixel, the same way CRTSubcarrierPhase in
ScyllaCRT.hlsl computes it.
Declaration
public static float SubcarrierPhase(float x, float scanline, float cyclesPerPixel, float lineShift, float fieldPhase)
Parameters
| Type | Name | Description |
|---|---|---|
| float | x | Raster column. |
| float | scanline | Raster row, which is one scanline. |
| float | cyclesPerPixel | Subcarrier cycles per raster pixel. |
| float | lineShift | Phase shift per scanline, in cycles. |
| float | fieldPhase | Phase of the current field, from FieldPhase(long, float). |
Returns
| Type | Description |
|---|---|
| float | The phase in cycles. Only its fraction matters. |
UsesTextureMask(CRTMaskType, bool, float)
Returns whether the mask is drawn from a texture rather than the procedural pattern: only for a mask whose triads reach TEXTURE_MASK_MIN_PERIOD, so every smaller mask stays exactly as it was.
Declaration
public static bool UsesTextureMask(CRTMaskType maskType, bool textureMask, float rawPeriod)
Parameters
| Type | Name | Description |
|---|---|---|
| CRTMaskType | maskType | The mask type. |
| bool | textureMask | Whether the settings allow the texture mask. |
| float | rawPeriod | The period from MaskPeriodRaw(float, int, int). |
Returns
| Type | Description |
|---|---|
| bool | True for the texture mask. |
Warp(Vector2, Vector2, CRTCurvatureShape, float)
Maps a position on the screen to the position on the raster that the curved tube shows there.
Declaration
public static Vector2 Warp(Vector2 centered, Vector2 curvature, CRTCurvatureShape shape, float overscan)
Parameters
| Type | Name | Description |
|---|---|---|
| Vector2 | centered | Screen position from (-1, -1) at the bottom left to (1, 1) at the top right. |
| Vector2 | curvature | Barrel strength per axis. |
| CRTCurvatureShape | shape | Faceplate shape. Cylindrical ignores the vertical curvature, flat ignores both. |
| float | overscan | Values above 1 push the raster past the visible edge. |
Returns
| Type | Description |
|---|---|
| Vector2 | The raster position in the same centered space. Values outside -1 to 1 lie beyond the edge of the raster, where the bezel shows. |
Remarks
Each axis is divided by 1 + curvature, so the middle of every edge still maps
to the edge of the raster and only the corners bend away.
The position is first divided by the overscan (never by less than 0.0001), then each axis
becomes p * (1 + k * r2) / (1 + k), where k is the curvature of that axis and
r2 is the squared distance of the position from the center.
WhitePointGain(float)
Computes a per-channel gain that shifts neutral white to the color of a black body at the given temperature, normalized so that 6500 K is (1, 1, 1).
Declaration
public static Vector3 WhitePointGain(float kelvin)
Parameters
| Type | Name | Description |
|---|---|---|
| float | kelvin | Color temperature in kelvin. |
Returns
| Type | Description |
|---|---|
| Vector3 | Linear RGB gains with the brightest channel at 1 or below. |
Remarks
Uses a published curve fit of the Planckian locus in sRGB (Tanner Helland, 2012). It is an approximation meant for a white point knob, not colorimetry.