如何用Python轮询支持VRR的显示器实时刷新率?
问题:如何获取VRR显示器的实时动态刷新率?
我正在开发一款Python工具,用来轮询并显示支持VRR(G-Sync/Free-Sync)的显示器的实时VRR数值。目前的代码只能获取显示器的预设刷新率(会随系统/游戏内的刷新率设置改变,但不是动态波动的实时值),但我需要的是随场景需求动态变化的实时运行刷新率,请问该怎么实现?
当前代码:
import tkinter as tk import win32api class RefreshRateMonitor(tk.Tk): def __init__(self): super().__init__() self.label = tk.Label(self, text="Refresh rate:") self.label.pack() self.refresh_rate_timer = self.after(1000, self.update_refresh_rate) def update_refresh_rate(self): refresh_rate = get_refresh_rate(None) self.label.config(text=f"Refresh rate: {refresh_rate} Hz") self.refresh_rate_timer = self.after(1000, self.update_refresh_rate) def get_refresh_rate(device_name): display_settings = win32api.EnumDisplaySettings(device_name, -1) refresh_rate = display_settings.DisplayFrequency return refresh_rate app = RefreshRateMonitor() app.mainloop()
解决方案
你当前用的win32api.EnumDisplaySettings获取的是显示器的标称刷新率(即系统预设的固定值),无法捕捉VRR动态调整的实时帧速率。要获取实时VRR数值,需要调用Windows的DXGI API(DirectX Graphics Infrastructure),它能直接查询显示适配器当前输出的实际帧速率。
实现步骤
- 用
ctypes调用Windows原生的dxgi.dll接口,获取当前显示输出的实时帧率统计信息。 - 调整Tkinter的更新频率(从1000ms改为100ms),更贴合VRR的动态变化节奏。
- 通过连续两次获取帧统计数据,计算出实时刷新率。
完整代码
import tkinter as tk import ctypes from ctypes import wintypes # 定义DXGI相关结构体 class DXGI_FRAME_STATISTICS_MEDIA(ctypes.Structure): _fields_ = [ ("PresentCount", wintypes.UINT), ("PresentRefreshCount", wintypes.UINT), ("SyncRefreshCount", wintypes.UINT), ("SyncQPCTime", wintypes.LARGE_INTEGER), ("SyncGPUTime", wintypes.LARGE_INTEGER) ] # 加载DXGI系统库 dxgi = ctypes.WinDLL("dxgi.dll") # 定义DXGI函数的参数类型 dxgi.CreateDXGIFactory1.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p)] dxgi.IDXGIAdapter_EnumOutputs.argtypes = [ctypes.c_void_p, wintypes.UINT, ctypes.POINTER(ctypes.c_void_p)] dxgi.IDXGIOutput_GetFrameStatisticsMedia.argtypes = [ctypes.c_void_p, ctypes.POINTER(DXGI_FRAME_STATISTICS_MEDIA)] def get_real_time_refresh_rate(adapter_idx=0, output_idx=0): """获取指定显示器的实时VRR刷新率""" # 创建DXGI工厂实例 factory = ctypes.c_void_p() if dxgi.CreateDXGIFactory1(ctypes.byref(ctypes.c_void_p(ctypes.addressof(ctypes.c_void_p()))), ctypes.byref(factory)) != 0: return 0.0 # 枚举显卡适配器 adapter = ctypes.c_void_p() if dxgi.IDXGIAdapter_EnumOutputs(factory, adapter_idx, ctypes.byref(adapter)) != 0: return 0.0 # 枚举显示器输出 output = ctypes.c_void_p() if dxgi.IDXGIAdapter_EnumOutputs(adapter, output_idx, ctypes.byref(output)) != 0: return 0.0 # 连续获取两次帧统计数据用于计算刷新率 stats_prev = DXGI_FRAME_STATISTICS_MEDIA() dxgi.IDXGIOutput_GetFrameStatisticsMedia(output, ctypes.byref(stats_prev)) ctypes.windll.kernel32.Sleep(1) # 等待极短时间确保有帧变化 stats_curr = DXGI_FRAME_STATISTICS_MEDIA() dxgi.IDXGIOutput_GetFrameStatisticsMedia(output, ctypes.byref(stats_curr)) # 计算时间差与刷新率差值 time_diff = (stats_curr.SyncQPCTime.QuadPart - stats_prev.SyncQPCTime.QuadPart) / 10_000_000 # 转换为秒 refresh_diff = stats_curr.PresentRefreshCount - stats_prev.PresentRefreshCount if time_diff > 0 and refresh_diff > 0: return refresh_diff / time_diff # 失败时 fallback到标称刷新率 display_settings = ctypes.windll.user32.EnumDisplaySettingsW(None, -1) return display_settings.DisplayFrequency class VRRMonitor(tk.Tk): def __init__(self): super().__init__() self.title("VRR实时刷新率监控") self.label = tk.Label(self, text="实时刷新率: -- Hz", font=("Arial", 20)) self.label.pack(padx=20, pady=20) self.update_timer = self.after(100, self.update_refresh_rate) def update_refresh_rate(self): current_rate = get_real_time_refresh_rate() self.label.config(text=f"实时刷新率: {current_rate:.1f} Hz") self.update_timer = self.after(100, self.update_refresh_rate) if __name__ == "__main__": app = VRRMonitor() app.mainloop()
关键说明
- 核心原理:
IDXGIOutput_GetFrameStatisticsMedia获取的PresentRefreshCount是显示器实际刷新的次数,结合时间差计算出的刷新率就是VRR动态调整的实时值。 - 兼容性:需要Windows 10及以上版本(DXGI 1.2+支持该接口),且显示器需开启G-Sync/Free-Sync功能。
- 多显示器适配:修改
adapter_idx和output_idx参数可切换监控的显示器,默认监控主显示器。
内容的提问来源于stack exchange,提问作者Jimmy
相关产品推荐
相关产品推荐

