I built physics-model-based RetroArch shaders for the original Game Boy and AGS-101 LCDs
▲ 126 r/SBCGaming+1 crossposts

I built physics-model-based RetroArch shaders for the original Game Boy and AGS-101 LCDs

I recently built a pair of RetroArch shaders that try to recreate the original display characteristics of the first-generation Game Boy and the backlit GBA SP AGS-101.

Many handheld shaders begin with an imagined “retro screen” color and then blend a few previous frames to create ghosting. That can look nostalgic, but nostalgia is not always accuracy.

Some Game Boy shaders become extremely bright green. Others look more like a generic calculator or electronic-organizer LCD than an original Game Boy. Both may feel old, but they are still interpretations of what people remember an old screen looking like.

I wanted to approach the problem differently.

Here are two screenshots with my Game Boy shader enabled. The first scene exposes row and column crosstalk, while the falling piece in the second image makes the LCD response and ghosting easier to see.

https://preview.redd.it/71j5mhvtfdkh1.png?width=2046&format=png&auto=webp&s=7e939fbd923bf198fc91c9d71e77992c06d9e45d

My approach can be summarized in four words:

Measure first, then calculate.

For color, I used measured and color-managed reference data for the original displays, including their color, grayscale and black-level behavior. That data is then converted correctly to sRGB instead of being approximated with a green tint.

On a reasonably accurate sRGB display, the result should therefore be much closer to the original screen colors—not simply someone’s memory of what a Game Boy looked like.

The second part is the display model itself.

A real LCD does not create ghosting by blending several complete frames. Each liquid-crystal pixel has a previous state, receives an electrical drive signal and takes time to move toward its next state. Bright-to-dark and dark-to-bright transitions do not necessarily happen at the same speed.

My shader first converts the game image into simulated electrical drive values. It then calculates how the virtual liquid-crystal pixels respond over time while preserving their previous states.

For the original Game Boy, the model also accounts for the shared row and column drive structure of its passive-matrix screen. This means ghosting, crosstalk and residual images are produced by the evolving display state rather than being painted over the final image as a blur effect.

The GBA SP AGS-101 uses a very different type of LCD, so I built a separate model for it. Its pixels respond faster, but different color and brightness transitions still have different response characteristics. Its measured color behavior, black level, scanning and pixel structure are handled separately from the Game Boy model.

The comparison below shows raw emulator output on the left and the AGS-101 shader on the right. The difference is not only saturation: the color balance, black level, tonal depth and pixel appearance all change.

https://preview.redd.it/pe1yosc6gdkh1.png?width=2040&format=png&auto=webp&s=774b5dfb3e5f5a1f40bcb2829bbbba37669125c2

The models were not tuned only by eye. During development, I used original display measurements, drive and timing information, historical LCD research, and experimental data from similar materials and panels from the same era.

Where complete original panel data no longer exists, I used historically appropriate research to constrain the model instead of inventing values based on appearance alone.

All screenshots in this post were rendered in RetroArch on a KONKR Pocket Advance and captured directly from its 960×640 framebuffer:

- GBA content uses an exact 4× mapping from 240×160 to 960×640.
- Game Boy content uses an exact 4× mapping from 160×144 to a 640×576 viewport.

These are direct captures of the shader’s GPU output on the handheld, not photographs of its physical panel.

The project is now open source and can be used with RetroArch. Download it, extract it into the RetroArch shaders directory, and load the appropriate preset. Any frame-mixing option inside the emulator core should be disabled so the temporal response is not calculated twice.

GitHub and download: https://github.com/JohnnySun/retro-display-lab

u/johnnymoe — 8 hours ago

Game Boy and GBA SP (AGS-101) shaders that keep causal per-pixel LCD state instead of blending N previous frames

Most handheld shaders I've used are a palette plus a dot grid, and the ones that do ghosting blend a fixed number of previous frames. That gets you into the neighbourhood, but it truncates the history at whatever N you picked, and every transition ends up behaving the same way.

I wanted to try the other approach: keep real per-pixel state and let it evolve.

There are two models in the repo.

Before / after. Same device, core, ROM and viewport in each pair, but not the same emulation frame — a still can't really show temporal decay.

Game Boy (DMG-01):

  • off:

https://preview.redd.it/z5u2yv3ndzjh1.png?width=960&format=png&auto=webp&s=abafae9694afe2a77f1b9b3ab4010f12e6b1f7c2

  • on:

https://preview.redd.it/rhlksu3ndzjh1.png?width=960&format=png&auto=webp&s=d805fb5e8b4cc88f06afcfac4b49d6e92d75b7cf

GBA SP (AGS-101):

  • off:

https://preview.redd.it/wp3p0livdzjh1.png?width=960&format=png&auto=webp&s=146ba7cde88fd7a5a7924314db6332d1f00bcd01

  • on:

https://preview.redd.it/6ixcwlivdzjh1.png?width=960&format=png&auto=webp&s=381cd16ee06c6400dd1171d45a2656d0d584d30a

One disclosure on that last one: it was captured from a private build whose colour stage uses measurement-derived data I can't redistribute. The public preset reaches a similar place with a neutral sRGB adapter instead.

DMG-01 (reflective passive-matrix STN)

  • fast and slow optical response over the whole frame history, not a fixed window
  • darkening and clearing run at different speeds (0.42 vs 0.23 per frame in the reference preset), and individual gray-to-gray transitions can differ
  • ionic / residual-DC state that builds up with exposure and releases on a much longer timescale — the slow image sticking — constrained against a 1994 STN experiment
  • sparse row and column crosstalk
  • rectangular aperture and reflector shadow
  • the four shades a game can address, plus a separate optical background for when the LCD is disabled

3 passes. The state lives in a single float feedback texture at source resolution (160x144), so there's no history stack to carry around and the cost is negligible.

AGS-101 (GBA SP TFT)

  • an analytic BGR aperture, integrated over the output-pixel footprint, so the subpixel structure and the gaps between pixels fall out of the model instead of being a grid drawn on top. The polynomial integration method is cgwg's from lcd-grid-v2 — re-derived rather than copied, and credited in the evidence map.
  • transition-dependent gray-to-gray response per subpixel, asymmetric (rise 0.620, fall 0.450 in the seed), following the TFT overdrive literature rather than a fixed N-frame blend
  • a slow signed residual-DC / image-sticking state on its own timescale
  • 2 passes, same feedback-texture approach

It ships as physics-seed-v1, not reference-v1, and that naming is

deliberate: nobody has published a full gray-to-gray matrix for an identified

AGS-101 panel, so the temporal constants are literature-constrained candidates,

not panel measurements.

The part I care about more than the look: every mechanism and parameter is tied

back to a measurement, to primary literature, or to an assumption that is

labelled as an assumption. Where I couldn't find the original panel's drive

waveform or response matrix, the repo publishes the literature constraint, the

candidate value and the uncertainty instead of dressing a derived number up as

a measurement. There's an evidence map per model saying which source backs

which line of code.

Limits, up front:

  • the AGS colour stage in the public preset is a neutral sRGB adapter. The measurement-derived colour work is based on a snapshot with no confirmed redistribution license, so that data doesn't ship here. Replacing it with independently measured colour is the plan.
  • the aperture model is prior-art-informed structure, not a microscope measurement of an identified panel's fill factor or subpixel order.
  • the one target profile I ship is for a KONKR GT78-VN, and that display state is sRGB-neutral and *unmeasured*. It is not calibrated sRGB and I don't claim it is. On any other display, start from the model preset and build your own target profile — the final pass is viewport-scaled, so it isn't tied to a particular output resolution.

Install: put it in RetroArch/shaders/, use the Vulkan driver, turn OFF the core's own frame mixing (otherwise the temporal response gets simulated twice), then load a .slangp.

Apache-2.0, source and evidence maps here:

https://github.com/JohnnySun/retro-display-lab

Feedback welcome, especially from anyone who has actual measurements off a DMG

or AGS panel — that's the biggest gap right now.

reddit.com
u/johnnymoe — 2 days ago