You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GDK3/GTK3窗口更新的精准计时技术问询

获取GTK绘制内容实际显示到屏幕的精准时间

你的问题切中了GTK绘制流程里一个容易被忽略的点:draw回调的执行时间和内容真正出现在屏幕上的时间之间,确实存在合成器调度的延迟(也就是你观察到的3个刷新间隔)。要拿到这个精准的显示时间,或者实现类似wait_for_screen_flip()的阻塞逻辑,GDK其实已经提供了跨合成器的抽象方案,不需要直接操作X11或Wayland的底层API。

核心方案:利用GdkFrameClock的Presentation回调

GDK的GdkFrameClock不仅负责帧的调度,还提供了presentation回调——这个回调会在当前绘制的帧实际被合成器显示到屏幕上时触发,这正是你需要的时间点。这个机制是跨X11和Wayland的,GDK会自动适配不同的合成器后端。

步骤1:在Draw回调中注册Presentation回调

每次在draw回调完成绘制后,你可以给当前的帧时钟注册一个presentation回调,用来捕获帧显示的精准时间。这里可以传递自定义数据(比如用来记录时间的变量、条件变量等):

#include <gtk/gtk.h>
#include <time.h>

// 自定义数据结构,用来传递给presentation回调
typedef struct {
    GCond *flip_cond;    // 用于阻塞等待的条件变量
    gint64 presentation_time; // 存储实际显示时间(微秒,CLOCK_MONOTONIC)
} PresentationData;

static void presentation_callback(GdkFrameClock *clock, gint64 predicted_presentation_time, gpointer user_data) {
    PresentationData *data = (PresentationData *)user_data;
    
    // 获取精准的CLOCK_MONOTONIC时间(比predicted更准确)
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    data->presentation_time = ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
    
    g_print("帧实际显示时间(CLOCK_MONOTONIC):%" G_GINT64_FORMAT " 微秒\n", data->presentation_time);
    
    // 如果是阻塞等待场景,唤醒等待的线程
    g_cond_signal(data->flip_cond);
    
    // 释放自定义数据
    g_free(data);
}

static gboolean draw_callback(GtkWidget *widget, cairo_t *cr, gpointer user_data) {
    // 你的绘制逻辑:切换窗口颜色等
    static gboolean is_black = TRUE;
    if (is_black) {
        cairo_set_source_rgb(cr, 0, 0, 0);
    } else {
        cairo_set_source_rgb(cr, 1, 1, 1);
    }
    cairo_paint(cr);
    is_black = !is_black;
    
    // 获取当前帧时钟
    GdkFrameClock *frame_clock = gtk_widget_get_frame_clock(widget);
    
    // 创建自定义数据,传递给presentation回调
    PresentationData *pres_data = g_new(PresentationData, 1);
    pres_data->flip_cond = (GCond *)user_data;
    pres_data->presentation_time = 0;
    
    // 注册presentation回调
    gdk_frame_clock_add_presentation_callback(frame_clock, presentation_callback, pres_data, g_free);
    
    return TRUE;
}

步骤2:实现阻塞等待屏幕翻转的逻辑

如果需要在单独线程中等待特定帧显示到屏幕,可以结合GLib的GCond和GMutex来实现类似wait_for_screen_flip()的功能:

void *flip_wait_thread(void *user_data) {
    GCond *flip_cond = (GCond *)user_data;
    GMutex *mutex = g_mutex_new();
    
    // 阻塞等待,直到presentation回调唤醒
    g_mutex_lock(mutex);
    g_cond_wait(flip_cond, mutex);
    g_mutex_unlock(mutex);
    
    g_print("等待线程:帧已显示到屏幕\n");
    
    g_mutex_free(mutex);
    return NULL;
}

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);
    
    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    GtkWidget *drawing_area = gtk_drawing_area_new();
    
    gtk_window_fullscreen(GTK_WINDOW(window));
    gtk_container_add(GTK_CONTAINER(window), drawing_area);
    
    // 初始化条件变量
    GCond *flip_cond = g_cond_new();
    
    // 设置draw回调
    g_signal_connect(drawing_area, "draw", G_CALLBACK(draw_callback), flip_cond);
    
    // 启动等待线程
    g_thread_new("flip-wait-thread", flip_wait_thread, flip_cond);
    
    // 启动tick回调触发重绘
    gtk_widget_add_tick_callback(drawing_area, (GtkTickCallback)gtk_widget_queue_draw, NULL, NULL);
    
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    
    gtk_widget_show_all(window);
    gtk_main();
    
    g_cond_free(flip_cond);
    return 0;
}

关键细节说明

  1. 时间精度:clock_gettime(CLOCK_MONOTONIC)可以提供纳秒级精度,转换为微秒后完全满足你毫秒级的需求。GDK传递的predicted_presentation_time是预测的显示时间,实际显示时间建议用clock_gettime直接获取。
  2. 跨合成器兼容性:gdk_frame_clock_add_presentation_callback()是GDK的抽象API,X11后端会通过Presentation扩展获取显示时间,Wayland后端则通过wp_presentation协议实现,不需要你针对不同平台做适配。
  3. 线程安全:GTK的所有API必须在主线程调用,所以presentation回调是在主线程触发的,而等待线程只负责条件变量的操作,不会直接调用GTK API,避免了线程安全问题。
  4. 资源清理:记得在presentation回调中释放自定义数据,避免内存泄漏;条件变量和互斥锁也要在程序退出时正确释放。

为什么之前的方法有延迟?

你在draw回调中直接测量时间,得到的是GTK完成绘制提交给合成器的时间,而合成器需要把这个帧加入渲染队列,等待下一个屏幕刷新周期才能显示——这中间就会有1~N个刷新间隔的延迟,而presentation回调是在合成器真正把帧显示到屏幕后才触发的,所以能拿到最准确的时间点。

内容的提问来源于stack exchange,提问作者David

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:23:05