Incorrect generation of interlaced modes
Is interlacing support in libxcvt not officially supported, and not worth fixing? It's hard to actually test interlacing nowadays, since IIRC modern NVIDIA GPUs don't support interlacing at the hardware output level, and modern Linux AMDGPU drivers do not support interlacing unless you set amdgpu.dc=0
to pick the legacy non-atomic-modesetting display output path (breaking HDMI audio).
EDIT: I actually tried booting in amdgpu.dc=0
mode, and my VGA CRT (Gateway VX720, like Diamond Pro 710) wouldn't even recognize my interlaced output as a signal. Welp.
Non-integer vtotal
/* 11. Find total number of lines in vertical field */
mode_info->vtotal =
vdisplay_rnd + 2 * vmargin + vsync_and_back_porch + interlace +
CVT_MIN_V_PORCH;
You're adding a float interlace
with value 0 or 0.5, to a uint16_t vtotal
. This discards the fractional part.
Adding porches and vsync pulses to both fields
libxcvt currently calculates vsync_start
and vsync_end
by adding sync pulse durations to vdisplay
:
/* Fill in vsync values */
mode_info->vsync_start = mode_info->vdisplay + CVT_MIN_V_PORCH;
mode_info->vsync_end = mode_info->vsync_start + vsync;
In the current code, vdisplay
currently stores the active lines of both fields in a frame combined. And vsync_start
and vsync_end
are intended to store the sync start and end times, after you interweave both fields of a frame back together.
-
libxcvt_mode_info
stores the timings per frame, after weaving two adjacent fields together. In interlaced modes, the differences between 0,vdisplay
,vsync_start
, andvsync_end
must map to the total time spent in active, front porch, and sync, in both fields of a single frame combined (disclaimer, vsync_start/end are off by half a scanline per frame or two fields, see below). - According to the spreadsheet,
CVT_RB_VFPORCH
and the sync pulse duration are added to each field. So in your C++ code, when interlacing is active, you need to add2 * (CVT_RB_VFPORCH || vsync)
tovsync_start
andvsync_end
(since they represent the sum of both fields)... plus 0.5?!
How do we handle the off-by-0.5? I'm prefacing this by saying I don't know confidently the correct way to generate an interlaced mode using CVT. The PDF is underspecified, the spreadsheet is internally inconsistent (front and back porches add up to x.5, even though vblank and vsync are integers?!), and every other existing tool I've tried has its own problems (tomverbeure outputs half-height modelines, and Universal Modeline Calculator outputs too high front porch with or without interlacing on).
What I do know is:
- Assume an interlaced mode with an odd number of lines =N per field (2 frames), and an even number of active lines (you reaaaally shouldn't be using odd resolutions, let alone interlaced).
- In an interlaced mode, hsync and vsync pulses are perfectly periodic. Hsyncs happen every line, but vsyncs occur with a period of x.5 lines (because they occur every N/2 lines, and N is odd).
To model interlaced modes, I drew diagrams for two test "mini modelines" with odd vtotal (scanline count) per frame (2 fields), 1 active line per field, and the interlaced vblank placed after either the early field (with longer porches) or late field. (It doesn't matter, in both cases you should add 0.5 to round vsync start/stop up, not down.)
- In interlaced PC/X11 modes, all active lines begin on hsync boundaries. Both fields start their active period on an integer line, and they begin around half a frame apart. Since each full frame has an odd number of lines, it cannot be divided evenly into 2 fields, so one field is actually 1 line longer (measuring from active begin to blanking end) than the other field.
- A long field starts (its active period) half a line earlier (relative to vsync) than a short field, enters vblank half a line earlier (relative to vsync), has the same vsync start and stop times (relative to vsync), and ends (with the next field's active period) half a line later (relative to vsync).
- Again, every field's start, blanking, and end timings are always aligned with hsync, while vsync is half a line out of phase with hsync on every other field.
- I don't know if a short/late field comes after an aligned vsync, an interlaced vsync, or if this varies by mode.
How do we convert between an interlaced signal and a sequential modeline (representing the timings of two sequential fields)?
- Return to the diagram of the video signal.
- Assume vtotal is odd. This is always the case for interlaced modes properly generated according to cvt's PDF and spreadsheet (without "simple bug").
- First number all scanlines from 0, in increments of 2 modulo vtotal.
- You can rearrange all scanlines so they occur in sorted increasing order.
- You will end up with a vsync starting in the middle of a horizontal scanline, which modelines cannot and should not represent.
- Instead, write down sequential scanlines, then for each scanline write down the state (active, blank, or vsync) active at the beginning of the scanline.
- The vsync now begins on an integer scanline, half a scanline later than it did in the naively rearranged diagram. (This is the case regardless of whether the long or short frame starts after the interlaced vblank, so I didn't recreate both diagrams on PC.)
I think this is the most logical way to position the vsync pulse, since it now starts at row 4, matching the interlaced signal where the two vsync pulses started on either row 4 or halfway between rows 3 and 5.
If you implement the same trick in interlaced modeline calculation, you have to add 1 to vsync_start
, and carry it through vsync_end
as well.
You could implement it something like:
static int interlace(bool interlaced, int hy) {
return interlaced ? (2 * hy + 1) : hy;
}
mode->vsync_start = mode->vdisplay + interlace(interlaced, CVT_MIN_V_PORCH);
Now if you want to handle esoteric corner cases, you can handle interlacing of odd-height resolutions as well. In this case, I think you shouldn't add an extra 1 to vsync_start
. (Good luck explaining this!) One clever way to implement this is:
mode->vsync_start = interlace(interlaced, vdisplay_rnd + CVT_MIN_V_PORCH);
When interlacing an odd vertical resolution, 2 * vdisplay_rnd
is 1 less than the original mode->vdisplay
, which perfectly cancels out the + 1
at the end to equal mode->vdisplay + 2 * CVT_MIN_V_PORCH
exactly.
Worked code
I've pushed my code to https://gitlab.freedesktop.org/nyanpasu64/libxcvt, though the branch also contains fixes to other bugs.
- I have not opened a PR yet. Ideally I'd do so after the other two bugs are fixed.
- libxcvt has a great deal of type conversion sloppiness, and no unit tests or assertions. I worked out the CVT interlacing calculations myself on https://codeberg.org/nyanpasu64/auto-modeline/src/branch/cvt/src/cvt.rs, which has many self-consistency assertions (most notably
assert_eq!(_vporch_end_y as f32, vtotal_frame_y)
) as well as regular/interlacing unit tests. Then I ported the logic to the C code and verified the results match.- I actually didn't implement CVT-RB in my Rust code, so I'm mostly mirroring my CVT changes to the RB codepath and hoping it works as well. But I struggle to think of a valid use case for CVT (CRT/LCD monitors, unsupported on many TVs) with reduced blanking (LCD monitors and possibly TVs, unsupported on CRT) and interlacing (TVs and CRT monitors, unsupported on LCD monitors).
Reduced blanking at alternative frame rates
In fact I just tried cvt 640 480 30 -i -r
and saw ERROR: Multiple of 60Hz refresh rate required for reduced blanking.
(complete with two spaces). I really don't think this error message should be in place:
- The CVT-RB1 PDF says "Although the standard Refresh Rate is 60Hz, it does not exclude other refresh rates from existing."
- My 2560x1440 monitor (Lenovo C32q-20) supports 1440p at 60 Hz using CVT-RB1, and 1440p at 75 Hz using a modeline very similar to 60hz:
2560x1440 (0x4a) 241.500MHz +HSync -VSync *current +preferred
h: width 2560 start 2608 end 2640 total 2720 skew 0 clock 88.79KHz
v: height 1440 start 1443 end 1448 total 1481 clock 59.95Hz
2560x1440 (0x4b) 299.000MHz +HSync -VSync
h: width 2560 start 2608 end 2640 total 2720 skew 0 clock 109.93KHz
v: height 1440 start 1443 end 1448 total 1470 clock 74.78Hz
If I edit cvt.c to allow 75hz CVT-RB, the timings match CVT-RB perfectly except my monitor has a smaller vback porch than 60hz, and CVT-RB has a larger vback porch:
# 2560x1440 74.97 Hz (CVT 3.69M9-R) hsync: 111.86 kHz; pclk: 304.25 MHz
Modeline "2560x1440R" 304.25 2560 2608 2640 2720 1440 1443 1448 1492 +hsync -vsync