The Virtual Display Driver supports precise fractional refresh rates through a multiplier system, enabling accurate NTSC rates (59.94Hz, 29.97Hz, 23.976Hz) and other non-integer refresh rates.
Refresh Rate Multiplier System
Floating-point refresh rates use a multiplier to represent fractional values accurately:
Actual Refresh Rate = Nominal Rate × (Multiplier / 1000)
<MonitorMode>
<Width>1920</Width>
<Height>1080</Height>
<RefreshRate>59.940</RefreshRate>
<RefreshRateMultiplier>999</RefreshRateMultiplier>
<NominalRefreshRate>60</NominalRefreshRate>
</MonitorMode>
Actual refresh rate in Hz (for display purposes)
Multiplier value (0-1000):
1000 = Exact rate (no fractional component)
999 = NTSC rate (×0.999)
- Custom values for other fractional rates
Base refresh rate before multiplier is applied
Standard NTSC Rates
NTSC fractional rates use the 999/1000 multiplier:
59.94 Hz (NTSC 60Hz)
<MonitorMode>
<Width>1920</Width>
<Height>1080</Height>
<RefreshRate>59.940</RefreshRate>
<RefreshRateMultiplier>999</RefreshRateMultiplier>
<NominalRefreshRate>60</NominalRefreshRate>
</MonitorMode>
Calculation: 60 × (999/1000) = 59.94 Hz
29.97 Hz (NTSC 30Hz)
<MonitorMode>
<Width>3840</Width>
<Height>2160</Height>
<RefreshRate>29.970</RefreshRate>
<RefreshRateMultiplier>999</RefreshRateMultiplier>
<NominalRefreshRate>30</NominalRefreshRate>
</MonitorMode>
Calculation: 30 × (999/1000) = 29.97 Hz
23.976 Hz (Film Rate)
<MonitorMode>
<Width>4096</Width>
<Height>2160</Height>
<RefreshRate>23.976</RefreshRate>
<RefreshRateMultiplier>999</RefreshRateMultiplier>
<NominalRefreshRate>24</NominalRefreshRate>
</MonitorMode>
Calculation: 24 × (999/1000) = 23.976 Hz
Custom Fractional Rates
For non-NTSC fractional rates, calculate custom multipliers:
47.952 Hz (Double Film Rate)
<MonitorMode>
<Width>1920</Width>
<Height>1080</Height>
<RefreshRate>47.952</RefreshRate>
<RefreshRateMultiplier>999</RefreshRateMultiplier>
<NominalRefreshRate>48</NominalRefreshRate>
</MonitorMode>
119.88 Hz (NTSC 120Hz)
<MonitorMode>
<Width>1920</Width>
<Height>1080</Height>
<RefreshRate>119.880</RefreshRate>
<RefreshRateMultiplier>999</RefreshRateMultiplier>
<NominalRefreshRate>120</NominalRefreshRate>
</MonitorMode>
Internal V-Sync Calculation
The driver converts fractional rates to v-sync ratios using GCD (Greatest Common Divisor) reduction:
// Driver.cpp:1445-1453
void float_to_vsync(float refresh_rate, int& num, int& den) {
den = 10000;
num = static_cast<int>(round(refresh_rate * den));
int divisor = gcd(num, den);
num /= divisor;
den /= divisor;
}
Example: 59.94 Hz
Input: 59.94 Hz
1. num = 59.94 × 10000 = 599400
2. den = 10000
3. GCD(599400, 10000) = 600
4. Reduced: num = 999, den = 16.67
5. Final ratio: 999:1000
Complete Configuration Examples
Multi-Rate 4K Configuration
<MonitorModes>
<!-- Exact rates -->
<MonitorMode>
<Width>3840</Width>
<Height>2160</Height>
<RefreshRate>24.000</RefreshRate>
<RefreshRateMultiplier>1000</RefreshRateMultiplier>
<NominalRefreshRate>24</NominalRefreshRate>
</MonitorMode>
<!-- NTSC film rate -->
<MonitorMode>
<Width>3840</Width>
<Height>2160</Height>
<RefreshRate>23.976</RefreshRate>
<RefreshRateMultiplier>999</RefreshRateMultiplier>
<NominalRefreshRate>24</NominalRefreshRate>
</MonitorMode>
<!-- Exact 30Hz -->
<MonitorMode>
<Width>3840</Width>
<Height>2160</Height>
<RefreshRate>30.000</RefreshRate>
<RefreshRateMultiplier>1000</RefreshRateMultiplier>
<NominalRefreshRate>30</NominalRefreshRate>
</MonitorMode>
<!-- NTSC 30Hz -->
<MonitorMode>
<Width>3840</Width>
<Height>2160</Height>
<RefreshRate>29.970</RefreshRate>
<RefreshRateMultiplier>999</RefreshRateMultiplier>
<NominalRefreshRate>30</NominalRefreshRate>
</MonitorMode>
<!-- Exact 60Hz -->
<MonitorMode>
<Width>3840</Width>
<Height>2160</Height>
<RefreshRate>60.000</RefreshRate>
<RefreshRateMultiplier>1000</RefreshRateMultiplier>
<NominalRefreshRate>60</NominalRefreshRate>
</MonitorMode>
<!-- NTSC 60Hz -->
<MonitorMode>
<Width>3840</Width>
<Height>2160</Height>
<RefreshRate>59.940</RefreshRate>
<RefreshRateMultiplier>999</RefreshRateMultiplier>
<NominalRefreshRate>60</NominalRefreshRate>
</MonitorMode>
</MonitorModes>
High Refresh Gaming Setup
<MonitorModes>
<!-- 144Hz exact -->
<MonitorMode>
<Width>2560</Width>
<Height>1440</Height>
<RefreshRate>144.000</RefreshRate>
<RefreshRateMultiplier>1000</RefreshRateMultiplier>
<NominalRefreshRate>144</NominalRefreshRate>
</MonitorMode>
<!-- 165Hz exact -->
<MonitorMode>
<Width>2560</Width>
<Height>1440</Height>
<RefreshRate>165.000</RefreshRate>
<RefreshRateMultiplier>1000</RefreshRateMultiplier>
<NominalRefreshRate>165</NominalRefreshRate>
</MonitorMode>
<!-- 240Hz exact -->
<MonitorMode>
<Width>1920</Width>
<Height>1080</Height>
<RefreshRate>240.000</RefreshRate>
<RefreshRateMultiplier>1000</RefreshRateMultiplier>
<NominalRefreshRate>240</NominalRefreshRate>
</MonitorMode>
</MonitorModes>
EDID Profile Integration
EDID profiles automatically include fractional rates with correct multipliers:
<!-- From monitor_profile.xml -->
<MonitorMode>
<Width>720</Width>
<Height>480</Height>
<RefreshRate>59.940</RefreshRate>
<RefreshRateMultiplier>999</RefreshRateMultiplier>
<NominalRefreshRate>60</NominalRefreshRate>
</MonitorMode>
The EDID parser (edid_parser.txt:80-87) automatically calculates multipliers:
int calculate_z_multiplier(double actual_refresh, int nominal_refresh) {
// For standard NTSC fractional rates (59.94, 29.97, 23.976)
if (std::abs(actual_refresh - nominal_refresh * 1000.0/1001.0) < 0.01) {
return 999; // NTSC factor
}
return 1000; // Exact refresh rates
}
Filtering Fractional Rates
When using auto resolutions, you can exclude fractional rates:
<auto_resolutions>
<enabled>true</enabled>
<edid_mode_filtering>
<exclude_fractional_rates>true</exclude_fractional_rates>
</edid_mode_filtering>
</auto_resolutions>
This filters out all modes where RefreshRateMultiplier ≠ 1000.
Global Refresh Rate Application
Apply specific refresh rates globally to all resolutions:
<global>
<g_refresh_rate>60</g_refresh_rate>
<g_refresh_rate>120</g_refresh_rate>
<g_refresh_rate>144</g_refresh_rate>
</global>
Global refresh rates always use exact rates (multiplier = 1000). For fractional rates, define them explicitly in individual resolution entries.
Refresh Rate Limits
Constrain refresh rates when using EDID or auto-resolution features:
<auto_resolutions>
<edid_mode_filtering>
<min_refresh_rate>24</min_refresh_rate>
<max_refresh_rate>240</max_refresh_rate>
</edid_mode_filtering>
</auto_resolutions>
Filtering applies to the NominalRefreshRate value, so both 60Hz and 59.94Hz would be included if the range includes 60.
Common Refresh Rates Reference
| Rate | Type | Multiplier | Nominal | RefreshRate |
|---|
| 23.976 Hz | Film (NTSC) | 999 | 24 | 23.976 |
| 24.000 Hz | Film (Exact) | 1000 | 24 | 24.000 |
| 25.000 Hz | PAL | 1000 | 25 | 25.000 |
| 29.970 Hz | NTSC | 999 | 30 | 29.970 |
| 30.000 Hz | Exact | 1000 | 30 | 30.000 |
| 47.952 Hz | Film 2× | 999 | 48 | 47.952 |
| 50.000 Hz | PAL | 1000 | 50 | 50.000 |
| 59.940 Hz | NTSC | 999 | 60 | 59.940 |
| 60.000 Hz | Exact | 1000 | 60 | 60.000 |
| 119.880 Hz | NTSC 2× | 999 | 120 | 119.880 |
| 120.000 Hz | Exact | 1000 | 120 | 120.000 |
| 144.000 Hz | Gaming | 1000 | 144 | 144.000 |
| 165.000 Hz | Gaming | 1000 | 165 | 165.000 |
| 240.000 Hz | High-refresh | 1000 | 240 | 240.000 |
VIC Mode Support
The EDID parser includes VIC (Video Identification Code) lookup tables for standard fractional rates (edid_parser.txt:436-481):
static const std::map<uint8_t, VICMode> vic_table = {
{2, {720, 480, 60, true}}, // 720p NTSC (59.94Hz)
{4, {1280, 720, 60, true}}, // 720p NTSC
{5, {1920, 1080, 60, true}}, // 1080p NTSC
{32, {1920, 1080, 24, true}}, // 1080p Film
// ...
};
The true flag indicates fractional rate support (multiplier = 999).
Troubleshooting
Refresh rate not showing correctly:
- Verify
RefreshRateMultiplier is set correctly (999 for NTSC, 1000 for exact)
- Check that
NominalRefreshRate matches the base rate
- Ensure
RefreshRate calculation is accurate
Application doesn’t detect fractional rate:
- Some applications only detect nominal rates
- Provide both exact and fractional versions of the same rate
Performance issues with fractional rates:
- Fractional rates require more precise timing
- Use exact rates (multiplier=1000) for better performance if fractional accuracy isn’t needed