You can handle borders, overscan cropping, and the different Genesis modes in the shader if you want to. See the following function, where t is equivalent to vTexCoord.x. This returns the chroma subcarrier value at t.
float chroma_carrier_genesis(float t, vec4 original_size) {
// The Genesis master clock (MCLK) is 53693175 Hz, and the chroma clock is
// MCLK/15 (3579545 Hz). In H40 mode (320 pixel width), each pixel in the
// active area is 8 cycles long[1]. In H32 mode (256 pixel width), each
// pixel is 10 cycles long.
//
// In both modes, a full line is 3420 cycles long. Because 3420 % 15 == 0,
// there is no phase offset between lines. With 262 lines per field[2],
// there is also no phase offset between fields. When interlacing, there
// are 262.5 lines per field. (3420 / 2) % 15 == 0, so there is no phase
// offset between fields when interlacing, either.
//
// [1] Note that in the blanking area, some pixels in H40 mode are
// different sizes. We can ignore that for our purposes.
// [2] There are some half lines in the blanking area which add to full
// lines.
//
// See: http://gendev.spritesmind.net/forum/viewtopic.php?f=2&t=3221
float carrier_normalized;
// TODO handle upscaled input and interlacing
if (original_size.x > 300) { // H40
carrier_normalized = 8.0 / 15.0 * original_size.x;
} else { // H32
carrier_normalized = 10.0 / 15.0 * original_size.x;
}
return PI * 2.0 * carrier_normalized * t;
}
Edit: Just to clarify, chroma_subcarrier * number_input_pixels / pixel_rate will give you the number of chroma periods across your input, whatever actual size it is. That’s all I am doing.