Gtk3和开罗动画抽搐

Posted

技术标签:

【中文标题】Gtk3和开罗动画抽搐【英文标题】:Gtk3 and cairo animation twitch 【发布时间】:2018-02-18 11:12:41 【问题描述】:

我用 gtk3 和 cairo 制作了一个非常简单的动画。每秒一次,它有点抽搐。这真的很烦人,而且看起来不太好。为什么会发生,我该如何解决?

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

static int width, height,
           posX = 0,
           vX = 2;
gboolean draw(GtkWidget* widget, cairo_t* cr)

    GtkWidget* window = gtk_widget_get_toplevel(widget);
    gtk_window_get_size(GTK_WINDOW(window), &width, &height);

    cairo_set_source_rgb(cr, 0, 0, 0);
    cairo_set_line_width(cr, 100);

    cairo_rectangle(cr, posX, height/2, 100, 100);
    cairo_stroke(cr);

    if(posX + vX >= width || posX + vX == 0)
        vX = -vX;
    posX += vX;

    gtk_widget_queue_draw(widget);
    return TRUE;

int main(int argc, char** argv)

    GtkWidget* window;
    GtkWidget* darea;

    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    darea = gtk_drawing_area_new();

    gtk_container_add(GTK_CONTAINER(window), darea);
    gtk_window_set_default_size(GTK_WINDOW(window), 500, 400);

    g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
    g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(draw), NULL);

    g_timeout_add(16, (GSourceFunc)draw, window);

    gtk_widget_show_all(window);
    gtk_main();

【问题讨论】:

【参考方案1】:

首先考虑您的原始程序在我的系统上完美运行,因此这可能是您系统上的问题。您可以尝试提高程序的优先级,但基本上您的代码容易受到此类问题的影响:请参阅g_timeout_add 了解基本原理。

无论如何,存在一个概念问题:您正在使用draw() 做两件事。它用作draw 信号和超时回调。这是错误的:draw 信号必须是幂等的,因为你不知道它何时以及调用了多少次。

这是您的示例,原始函数分为 move()draw()

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

static int width, height,
           posX = 0,
           vX = 2;

static gboolean move(GtkWidget* widget)

    GtkWidget* window = gtk_widget_get_toplevel(widget);
    gtk_window_get_size(GTK_WINDOW(window), &width, &height);

    if(posX + vX >= width || posX + vX == 0)
        vX = -vX;
    posX += vX;

    gtk_widget_queue_draw(widget);
    return TRUE;


static gboolean draw(GtkWidget* widget, cairo_t* cr)

    cairo_set_source_rgb(cr, 0, 0, 0);
    cairo_set_line_width(cr, 100);

    cairo_rectangle(cr, posX + 0.5, height/2 + 0.5, 100, 100);
    cairo_stroke(cr);

    return FALSE;


int main(int argc, char** argv)

    GtkWidget* window;
    GtkWidget* darea;

    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    darea = gtk_drawing_area_new();

    gtk_container_add(GTK_CONTAINER(window), darea);
    gtk_window_set_default_size(GTK_WINDOW(window), 500, 400);

    g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
    g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(draw), NULL);

    g_timeout_add(16, (GSourceFunc)move, window);

    gtk_widget_show_all(window);
    gtk_main();

不确定您的问题是否会得到解决,但无论如何都必须这样做。

【讨论】:

【参考方案2】:

我认为您遇到的问题是您使用 g_timeout_add 就好像它是一个精确的时间源,而不是文档所述:

请注意,由于其他事件源的处理,超时功能可能会延迟。因此,不应依赖它们来确定精确的时间。每次调用 timeout 函数后,根据当前时间和给定的时间间隔重新计算下一次超时的时间(它不会尝试“赶上”在延迟中丢失的时间)。

这意味着您在draw 回调中的代码可能会在每次调用时稍晚(或更晚)调用。由于未重新计算超时,错误累加。你会失去同步并在错误的位置上画。例如,在视频播放器中解码帧时会发生这种情况:如果帧解码时间过长,它可能会被丢弃,因为可能我们已经太晚了,需要显示下一帧。

我不确定什么是正确的解决方案,也许 GTK+(或真正为动画制作的 Clutter)开发人员可以给你一些提示,所以最好通过他们的 IRC 频道询问他们。

但是,我自己在编写节拍器时遇到了这个问题。如果您尝试与g_timeout_add 同步,则会出现错误并导致您失去同步。以下是我为我所做的工作。

首先,我在一开始就启动了GTimer,这样我就有了一个可靠、精确和绝对的时间参考。然后当我的回调被调用时,我:

计算自上次滴答以来经过的时间(在您的情况下自上一帧以来) 播放我的刻度(在您的情况下将对象显示在该时间的正确位置) 计算距离下一个刻度(下一帧)还有多长时间 使用该值调用 g_timeout_add(它会创建一个新的事件源) 返回G_SOURCE_REMOVE,这样当前的事件源就不再被调用(基本上我用新的替换它)

这是我的节拍器代码供参考:https://github.com/liberforce/metrognome/blob/master/metronome.c

您也可以使用g_timeout_add_full 代替g_timeout_add,这样您就可以使用更高的优先级。

我还推荐阅读 Owen Taylor 关于 gnome-shell 中的动画同步的系列文章:

https://blog.fishsoup.net/2011/06/13/benchmarking-compositor-performance/ https://blog.fishsoup.net/2011/06/22/what-to-do-if-you-cant-do-60fps/ https://blog.fishsoup.net/2011/06/30/frame-timing-the-simple-way/ https://blog.fishsoup.net/2011/11/08/applicationcompositor-synchronization/ https://blog.fishsoup.net/2012/11/28/avoiding-jitter-in-composited-frame-display/

【讨论】:

以上是关于Gtk3和开罗动画抽搐的主要内容,如果未能解决你的问题,请参考以下文章

css3微调器上的抽搐动画

如何在 Gtk3 中在开罗上下文中使用 Skia 绘制

关于开罗

如何使用 Cairo 和 Gtk3 在 GtkDrawingArea 中绘制一条线

为啥我在使用这个 UIView 动画块时会看到生涩的动画?

Gtk3 和 cairo g_timeout_add 不起作用