Skip to content
RetrogamingDeep Dive Published Updated 6 min readViews unavailable

CRT Shaders and Integer Scaling: Making Old Pixels Look Right on New Screens

How integer scaling, aspect ratio, scanlines, masks, bloom, interlacing, and sampling interact when reproducing CRT-era video on flat panels.

Classic consoles output low-resolution video modes by today’s standards — 256×224 is one common example, against a modern 4K panel’s 3840×2160. The console’s digital picture, analog encoder, television standard, cable, and display each shaped the final image. Scaling therefore raises more than one question: how should samples be enlarged, what aspect ratio should they occupy, and which display characteristics are worth reconstructing?

The naive approach: bilinear stretching

The default behavior of most image and video scaling is bilinear interpolation — blending neighboring source pixels smoothly to fill in a larger destination image. Applied to pixel art, this reliably produces a blurry, smeared result, because pixel art was authored with hard, deliberate edges between adjacent pixels that bilinear blending directly erases.

Integer scaling: crisp, but not authentic

Integer scaling fixes the blur by restricting magnification to exact whole-number multiples (2x, 3x, 4x…) and using nearest-neighbor sampling — every source pixel becomes a perfectly crisp NxN block of identical-colored pixels, with no blending at all:

Source (1 pixel):     Bilinear 4x:          Integer (nearest) 4x:
   ■                  ▓▓▒▒                  ■■■■
                       ▒▒░░                  ■■■■
                                              ■■■■
                                              ■■■■

This is a popular, simple default, and a real improvement over blur — but it isn’t actually how these games were designed to be seen, either. It shows the raw pixel grid with perfect fidelity, which is not the same thing as showing the image the original artists were looking at while they made it.

Integer scaling also creates a geometry trade-off. A 224-line source fits nine times into 2160 pixels, using 2016 lines and leaving borders. Filling the entire height requires a fractional scale or cropping. If a source dimension is not divided evenly, nearest-neighbor output gives some source pixels more destination pixels than others; during scrolling, that unevenness can appear as shimmer or rhythmic judder.

The right calculation must distinguish the emulator’s source buffer from the intended display aspect ratio. Many consoles emitted samples that televisions displayed with non-square effective geometry. Scaling 256 source columns to 256 perfectly square blocks can produce an image narrower than the intended 4:3 presentation. “Pixel perfect” can mean preserving the buffer’s sample grid or preserving the historical picture shape; those goals sometimes conflict.

What a CRT actually did to the image

Period software was commonly authored and tested for CRT televisions or monitors, which do not present the source as a modern grid of square, sample-and-hold pixels. The electron beam, phosphors, focus, video bandwidth, shadow mask or aperture grille, scan structure, and analog signal path influence the visible result. Adjacent samples can blend, alternating dither can read as another texture or color, and nominally black gaps may remain between illuminated scan lines.

There was no single “CRT look.” A consumer composite television, an RGB arcade monitor, a professional video monitor, and a VGA computer display differ in sharpness, mask pattern, scan frequency, phosphor behavior, color, overscan, geometry, and signal artifacts. Composite video can add dot crawl, color bleed, and luma/chroma interaction that an RGB-oriented shader should not invent. A shader preset is therefore a model of a chosen display chain, not a universal restoration switch.

What a CRT shader actually simulates

A CRT shader is a real-time graphics shader that recreates these specific physical effects on a modern flat panel: darkened or fully black scanline gaps at regular intervals, a subtle horizontal blur approximating shadow-mask blending, sometimes a mild barrel-distortion curvature and vignette matching a physical tube’s geometry, and phosphor glow bleeding slightly beyond each lit pixel’s edges:

scanline_brightness(y) = base_color * (1.0 - scanline_intensity * (y % 2))
horizontal_blend(x)    = mix(pixel[x], pixel[x+1], mask_blend_factor)

Done well, this doesn’t just add a nostalgic filter — it reconstructs the intended visual result the original artists were designing against and tuning their palettes and dithering for, which a bare, sharp pixel grid genuinely does not show.

That reconstruction is approximate. A flat panel cannot become an electron-beam display through a fragment shader, but it can reproduce selected spatial and temporal cues. Libretro’s CRT documentation groups shaders by scanline, phosphor-mask, blur, curvature, interlacing, glow, and halation behavior. MAME’s BGFX system similarly supports multi-pass post-processing chains and per-screen effects.

Why output resolution and brightness headroom matter

A simulated RGB mask needs several physical panel pixels for each repeated mask element. At 1080p, an elaborate mask may be too coarse, create moiré, or darken the image heavily; 1440p and 4K give the shader more samples with which to represent scanlines and phosphor structure. Integer scaling can stabilize repeating patterns, while some shaders deliberately support fractional scaling with filtering designed to reduce aliasing.

Darkening scanlines and masks reduces average brightness. Raising shader gain can clip highlights or distort colors, while simply increasing panel brightness changes black level and viewing comfort. HDR-capable output may offer additional headroom in some pipelines, but only if the frontend, shader, operating system, and display handle the signal consistently. Calibration should use test patterns and normal content, not memory alone.

Interlacing, field timing, and motion

Many systems use progressive low-resolution modes, interlaced modes, or switch between them. Treating every source as 240p can produce combing, incorrect vertical resolution, or flicker in menus and high-resolution screens. A shader that simulates interlacing must receive correct field order and timing from the emulator.

CRT motion characteristics also differ from sample-and-hold panels. Scanout and short phosphor illumination can look clearer in motion than a continuously held flat-panel frame. Black-frame insertion or strobing may approximate part of that behavior, but it reduces brightness and can introduce visible flicker. It is a display-timing feature, not something a static screenshot can demonstrate.

Build a reproducible scaling pipeline

Choose the core’s crop and overscan behavior first, then aspect ratio, scale, rotation, and shader. Applying those transformations in an accidental order can make a mask uneven or blur an already-scaled image. Keep presets per system or video mode rather than forcing one set of parameters onto arcade monitors, handheld LCDs, composite consoles, and computer displays.

Test scrolling grids, single-pixel text, checkerboard dithering, 240p and interlaced transitions, saturated colors, and dark scenes. Record frontend version, video driver, shader preset and parameters, output resolution, refresh rate, and display mode. GPU shader languages and driver behavior change, so a screenshot plus configuration is more reproducible than a preset name alone.

Performance matters because a complex multi-pass shader can make the emulator miss frame deadlines. Measure frame pacing and input latency with the shader enabled. If the GPU cannot sustain the target refresh rate, a simpler scanline or interpolation preset is more faithful than an elaborate model that stutters.

Why there’s no single “correct” choice

Integer scaling and CRT shading answer different questions: integer scaling shows you exactly what data the console produced, pixel for pixel; a CRT shader shows you what a period-accurate display would have done with that same data. Neither is “wrong” — they’re reconstructing two different, both-legitimate things, and most modern frontends (RetroArch prominent among them) expose both as configurable options rather than picking one as the default for every game and every player’s taste.

More precisely, integer scaling preserves a chosen source sample grid, while a shader approximates selected properties of a particular display and signal path. Users may reasonably prioritize crisp artwork, intended aspect ratio, composite blending, low latency, motion clarity, or a remembered household television. A good emulator labels those choices and avoids presenting subjective defaults as measured hardware truth.

Related:

Sources:

Comments