GBCC Tech Notes

This is a collection of notes about interesting things I've done for GBCC, or anything else in that vein.

Table of Contents

  1. Colour Correction
  2. Subpixel Rendering
  3. Interlacing

Colour Correction

The RGB subpixels on the Game Boy Color's display are not the same colour as those in a modern monitor. This leads to incorrect colours if we directly use the RGB values set by the GBC. This can be seen in the screenshot below.

Screenshot of Shantae in GBCC without colour correction.

Everything is way oversaturated, especially purples; look at the rocks at the bottom, or the shadow underneath the roof. Most emulators try to correct for this in some way, but it's especially difficult for the Game Boy Color, as the lack of a backlight means that the colours are dependent on ambient lighting.

For GBCC, I decided to use a lookup table, rather than try to figure out an equation for each RGB channel. To do this, however, I needed some way to recreate different colours on a real GBC, and know the values being used. The easiest way would be to write a test ROM, but custom flashcarts are expensive, so instead I bought a copy of Harvest Moon 3. This gives you the option to change the RGB value of your character's clothes.

Screenshot of the character customisation screen in Harvest Moon 3.

I sampled an 8×8×8 grid of colours (so 512 colours overall), and tried to best match each colour on a PC. Originally, I then used this to generate a 32×32×32 lookup table by trilinearly interpolating between neighbouring points, and then passing this as a Texture Buffer Object (TBO) to a shader. The shader would then convert input RGB values to an index into this buffer. This approach worked fine, and the results are shown below.

Screenshot of Shantae in GBCC with colour correction.

This is much nicer - the rocks are now rock-coloured, and the red and gold of the UI is no longer eye-bleeding. Some of the purples are slightly too grey compared to a real system, and Shantae's skin is a bit too dark, but the overall effect is pretty good.

An issue arose though, when I came to porting GBCC to Android. I had decided on a minimum requirement of OpenGL ES 3.0, as this allowed me to change as little as possible from the existing OpenGL 3.2 desktop code while still being compatible with the majority of devices. This came with one big drawback; OpenGL ES 3.0 doesn't support TBOs. I scoured the documentation for a data structure that could hold the lookup table, but nothing seemed suitable. The main problem was size; the only (or so I thought) suitable buffer object in ES 3.0 is a Uniform Buffer Object (UBO), and the spec only guarantees that the maximum UBO size must be at least 16 KiB (Table 6.33, Page 278). For a 32×32×32 lookup table with 4 bytes per colour, we have 128 KiB of data, making UBOs infeasible. There are other, larger buffer objects present in modern GL, such as SSBOs which have a "minimum maximum" size of 128 MiB (Table 20.47, Page 406) , but these are only present in ES 3.1 and later. Moving to ES 3.1 would mean sacrificing compatibility with ~29% of Android devices, which definitely didn't seem worth it.

After an ashamedly long time, and some thinking that this was impossible, I realised that I was making the issue much more complicated than it needed to be. I was looking at the lookup table as if it was a chunk of arbitrary data, when really what I had was a texture. OpenGL ES 3.0 supports 3D textures of at least 256 texels in each dimension, so GBCC's 32×32×32 lookup table would easily fit. I then also realised that I didn't even need a table that large; GPUs perform linear interpolation all of the time. Instead of manually interpolating my 8×8×8 table, I could just use that directly as a 3D texture to do the lookup, and let the graphics card handle the interpolation. In my favourite commit to GBCC so far, this both simplified the shader code, allowed me to delete all of the manual interpolation code, and fixed the Colour Correct shader on Android. Adding features by removing code feels good :)

Back to Table of Contents

Subpixel Rendering

As with most colour LCDs, the Game Boy Color's pixels are made up of three separate monochrome "subpixels". In the Game Boy's case, these are in the most common order, RGB from left → right. On a modern LCD, these subpixels are often too small to be seen with the naked eye, however on the Game Boy Color they're big enough to have a noticeable effect.

Take a look at Pikachu's face in the picture below, specifically his tongue and the red dots in his eyes.

Photo of Pikachu's face on a real Game Boy Color.

You should notice that his tongue is stripy, and the red dots in his eyes are thinner than a full pixel, causing them not to touch. Now look at the following screenshot, using the "Colour Correct" shader:

Screenshot of Pikachu's face in GBCC using the Colour Correct shader.

While this looks good, the tongue and cheeks are a solid red, and the red dots in the eyes are touching. One way to recreate the subpixel effect is to render at native resolution (160 × 144 px), however this obviously produces a tiny image on a modern HD monitor. Instead, we can use the much greater resolution of modern displays to render each GBC subpixel with multiple pixels.

Simulating an LCD effect by rendering individual subpixels isn't anything new in the world of emulation, but as far as I'm aware there isn't a dedicated application of this to the GBC's hardware. Using a generic LCD shader without colour-correcting will result in incorrect colours, as the Game Boy's subpixels are not the same "pure" RGB of today's monitors. If we do colour correction first, the subpixel effect is lost, as we need to activate multiple simulated "subpixels" to recreate the colour. For example, pure red on a GBC has an RGB value of something like #FF7145 — there is a significant amount of green and blue present.

The solution then is to simulate an LCD with subpixels the same colour as the Game Boy's, and this is what GBCC does. This also simplifies the colour correction, as we only need three values - the modern LCD RGB values of each of the GBC's subpixels. GBCC uses the following values:

The next question is: what shape do we give to the intensity profile of each subpixel? I'm not sure of the best answer to this one. Looking at the screen through a microscope, we can see that the subpixels are roughly rectangular:

Game Boy Color subpixels as seen through a microscope.

Our eyes can't really resolve the subpixels individually at normal viewing distances, so we see a blurred version of the above as "white". The correct profile then is probably a Gaussian-blurred rectangle, but this is a relatively complicated shape to calculate. At the time of writing, GBCC uses a semicircular profile of radius 2/7 of a pixel, with each subpixel spaced 2/7 of a pixel apart, leaving a small dark line on the right of each pixel. A semicircle is easy to calculate and isn't a bad fit for a blurred rectangle (plus it looks good at high resolution 🙂). The reason the fractions are in terms of 7ths is mostly due to this mapping on to whole pixels when displayed at 1080p (1080 / 144 = 7.5, so we can only scale the GBC screen up by an integer factor of 7 on a landscape 1080p display).

Plot of subpixel intensity profiles used in GBCC.

Curves showing the intensity of each subpixel across 1 screen pixel displaying pure white. The shaded region underneath shows the colour reproduced by GBCC, by adding each subpixel's colour weighted by the intensity profile. Ideally, there shouldn't be discontinuities, but this looks good at high resolutions so ¯\_(ツ)_/¯. Also note that the red quarter-circle on the right takes its colour value from that of the next pixel over.

Code to produce this plot

The image below shows the same screenshot again, with the "Subpixel" shader applied at 1080p (click the image to open it full size).

Screenshot of Pikachu's face in GBCC using the Subpixel shader.

Hopefully, by flicking between this screenshot and the photo from the beginning, you can agree that this is a pretty good likeness to a real GBC. The main caveat is that the colours look different for different scaling factors; this is mostly fine, but below 4× scaling things start to look noticeably wrong (for example, on a non-hi-dpi monitor, the above image is tinted yellow). At 1080p and above though, I'm fairly sure this is as close to a real GBC screen you can get with any current emulator.

Back to Table of Contents

Interlacing

Modern displays present their images using progressive scan, meaning that they draw the entire image every frame. Older displays such as CRTs drew images with interlacing; each frame they alternated between drawing only odd and only even rows of pixels. The Game Boy & Game Boy Color do something half way inbetween. While the full screen gets redrawn every frame, every second line is much dimmer, and each frame this alternates between even and odd lines. This leads to a few interesting effects, whenever something moves across the screen. Unfortunately, it's very difficult to capture this on video, as it needs to be displayed properly synced to the monitor. Hover over the image below to see this effect slowed down 30×.

Animation of interlacing effect in GBCC.

Clicking the image above will open it at full speed, though it will likely stutter in your browser - you'll just have to try it in GBCC! (or on your Game Boy).

Another issue with interlacing is that it necessarily dims the image somewhat. This is unavoidable, however the Game Boy screen was very dim anyway without a backlight.

Back to Table of Contents