-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathdisplay_mode.cpp
151 lines (124 loc) · 5.63 KB
/
display_mode.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#include "display_mode.h"
#include <fmt/core.h>
namespace pivid {
// Generated by display_mode_gen.py from data tables pasted from PDF docs;
// defines cta_861_modes and vesa_dmt_modes.
#include "display_mode.inc"
double DisplayMode::actual_hz() const {
if (!nominal_hz || !scan_size.x || !scan_size.y) return 0;
double const raw_hz = pixel_khz * 1000.0 / scan_size.x / scan_size.y;
return raw_hz * (doubling.y < 0 ? 2.0 : doubling.y > 0 ? 0.5 : 1.0);
}
// See the CVT v1.2 spec:
// https://app.box.com/s/vcocw3z73ta09txiskj7cnk6289j356b/file/93518784646
std::optional<DisplayMode> vesa_cvt_mode(XY<int> size, int hz) {
if (size.x <= 0 || size.y <= 0 || hz <= 0) return {};
// CVT 3.6 "Sync polarities", Table 3-2 "Vertical Sync Duration"
int const V_SYNC_RND =
(size.y * 4 == size.x * 3) ? 4 :
(size.y * 16 == size.x * 9) ? 5 :
(size.y * 16 == size.x * 10) ? 6 : 0;
if (!V_SYNC_RND) return {}; // Unsupported aspect ratio
// CVT 5.5 "Definition of Constants & Variables"
int const C_PRIME = 30;
double const CLOCK_STEP = 0.25;
int const H_SYNC_PER = 8;
int const M_PRIME = 300;
int const MIN_V_PORCH_RND = 3;
int const MIN_V_BPORCH = 6;
int const MIN_VSYNC_BP = 550;
// CVT 5.2 "Computation of Common Parameters"
// Interlacing and margins are considered silly, so most of this is moot.
if (size.x % 8) return {}; // X size must be a multiple of 8
// CVT 5.3 "Computation of "CRT" Timing Parameters"
// 8. Estimate the Horizontal Period (kHz):
double const H_PERIOD_EST =
((1.0 / hz) - MIN_VSYNC_BP / 1e6) / (size.y + MIN_V_PORCH_RND) * 1e6;
// 9. Find the number of lines in V sync + back porch:
int const V_SYNC_BP = std::max(
int(MIN_VSYNC_BP / H_PERIOD_EST) + 1,
V_SYNC_RND + MIN_V_BPORCH
);
// 10. Find the number of lines in V back porch:
int const V_BACK_PORCH = V_SYNC_BP - V_SYNC_RND;
// 11. Find total number of lines in Vertical Field Period:
int const TOTAL_V_LINES = size.y + V_SYNC_BP + MIN_V_PORCH_RND;
// 12. Find the ideal blanking duty cycle from the equation (%):
double const IDEAL_DUTY_CYCLE = C_PRIME - (M_PRIME * H_PERIOD_EST / 1e3);
// 13. Find the number of pixels in the horizontal blanking time:
int const H_BLANK = (IDEAL_DUTY_CYCLE < 20) ?
int(size.x / 64) * 16 :
int(size.x * IDEAL_DUTY_CYCLE / (100 - IDEAL_DUTY_CYCLE) / 16) * 16;
// 14. Find the total number of pixels in a line:
int const TOTAL_PIXELS = size.x + H_BLANK;
// (see CVT 3.4.1 "Standard CRT-based Timing" 5 "Horizonal Sync Pulse")
int const h_sync = (TOTAL_PIXELS * H_SYNC_PER / 100 / 8) * 8;
// 15. Find Pixel Clock Frequency (MHz):
double const ACT_PIXEL_FREQ = CLOCK_STEP *
int(TOTAL_PIXELS / H_PERIOD_EST / CLOCK_STEP);
if (ACT_PIXEL_FREQ < 12.5) return {}; // HDMI min (after doubling) is 25MHz
DisplayMode mode = {};
mode.size = size;
mode.scan_size = {TOTAL_PIXELS, TOTAL_V_LINES};
mode.sync_end = {size.x + H_BLANK / 2, TOTAL_V_LINES - V_BACK_PORCH};
mode.sync_start = {mode.sync_end.x - h_sync, TOTAL_V_LINES - V_SYNC_BP};
mode.sync_polarity = {-1, +1}; // CVT standard CRT
mode.doubling = {(ACT_PIXEL_FREQ < 25) ? 1 : 0, 0};
mode.pixel_khz = int(ACT_PIXEL_FREQ * 1000);
mode.nominal_hz = hz;
// CVT 3.6 "Sync polarities", Table 3-1
mode.aspect =
(mode.sync_end.y - mode.sync_start.y == 4) ? XY<int>{4, 3} :
(mode.sync_end.y - mode.sync_start.y == 5) ? XY<int>{16, 9} :
(mode.sync_end.y - mode.sync_start.y == 6) ? XY<int>{16, 10} :
XY<int>{};
return mode;
}
// See the CVT v1.2 spec:
// https://app.box.com/s/vcocw3z73ta09txiskj7cnk6289j356b/file/93518784646
std::optional<DisplayMode> vesa_cvt_rb_mode(XY<int> size, double target_hz) {
int const nominal_hz = (int)(target_hz + 0.5);
if (size.x <= 0 || size.y <= 0 || nominal_hz <= 0) return {};
// Vertical blank must be at least 460usec
double const nominal_line_sec = (1.0 / nominal_hz - 460e-6) / size.y;
int const vblank_y = std::max(15, int(460e-6 / nominal_line_sec) + 1);
// For RBv2, most timing parameters are fixed or simply derived
DisplayMode m = {};
m.size = size;
m.scan_size = {size.x + 80, size.y + vblank_y};
m.sync_start = {size.x + 8, m.scan_size.y - 14};
m.sync_end = {size.x + 40, m.scan_size.y - 6};
m.sync_polarity = {+1, -1}; // CVT Reduced Blanking
m.pixel_khz = int(m.scan_size.x * m.scan_size.y * target_hz / 1000);
m.doubling = {(m.pixel_khz < 25000) ? 1 : 0, 0};
m.nominal_hz = nominal_hz;
// HDMI min (after doubling) is 25MHz
if (m.pixel_khz * (m.doubling.x > 0 ? 2 : 1) < 25000) return {};
return m;
}
std::string debug(DisplayMode const& m) {
if (!m.nominal_hz) return "OFF";
return fmt::format(
"{:5}x{:<5} @{:<6.5g} {:5.4g}M{:2} "
"{:4}[{:3}{}]{:<3} {:2}[{:2}{}]{:<3}{}",
m.size.x,
fmt::format(
"{}{}", m.size.y,
m.doubling.y > 0 ? "p2" : m.doubling.y < 0 ? "i" : "p"
),
m.actual_hz(),
m.pixel_khz / 1000.0,
m.doubling.x > 0 ? "*2" : m.doubling.x < 0 ? "/2" : "",
m.sync_start.x - m.size.x,
m.sync_end.x - m.sync_start.x,
m.sync_polarity.x < 0 ? "-" : m.sync_polarity.x > 0 ? "+" : "",
m.scan_size.x - m.sync_end.x,
m.sync_start.y - m.size.y,
m.sync_end.y - m.sync_start.y,
m.sync_polarity.y < 0 ? "-" : m.sync_polarity.y > 0 ? "+" : "",
m.scan_size.y - m.sync_end.y,
m.aspect == XY<int>{0, 0} ? "" :
fmt::format(" {}:{}", m.aspect.x, m.aspect.y)
);
}
} // namespace pivid