GtkDrawingArea/cairo визуальные глюки

Я создаю приложение GTK+ 3, которое рисует анимацию с помощью Cairo в виджете GtkDrawingArea. Я получаю визуальные сбои, подобные тем, которые наблюдаются на изображениях ниже. Они появляются только для одного кадра, их может не быть ни одного, или один, или более одного на кадр. Прошу помощи в выявлении возможной проблемы. Вот подробности моего кода:

В моем основном методе перед запуском цикла gtk_main() я подключаю тайм-аут.

g_timeout_add(50, queue_draw, gtk_builder_get_object(builder, "window")); 

"window" — это идентификатор моего GtkWindow. Функция queue_draw выглядит следующим образом:

gboolean queue_draw(gpointer user_data)
{
  gtk_widget_queue_draw(GTK_WIDGET(user_data));
  return TRUE;
}

Я бы подумал, что могу передать этой функции объект GtkDrawingArea, а не весь GtkWindow, но в этом случае анимация исчезает. Меня также интересуют комментарии к такому поведению, но это не мой главный вопрос.

Сигнал отрисовки моего GtkDrawingArea привязан к функции gboolean drawing_area_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data). Внутри этого метода я рисую свою трехмерную гистограмму с помощью алгоритма ленивого художника, каждый столбец состоит из трех параллелограммов, а столбцы рисуются в порядке z.

Проблема не в том, что мой компьютер не справляется с рендерингом кадров, каким-то образом повреждая буфер кадров. Я установил тайм-аут на 1000 мс, чтобы захватить изображения ниже.

Я не звонил gtk_widget_set_double_buffered().

Мне не удалось обнаружить проблему в подсистеме Windows для Linux (WSL) с использованием XMing в качестве X-сервера, что заставляет меня думать, что это может быть проблема с библиотекой или какое-то плохо определенное поведение.

На первом изображении визуальный сбой во время нормальной работы моей программы. Во втором я изменил код и установил плавный градиент высоты столбцов. Это дает гораздо лучшее представление о проблеме, но все еще очень озадачивает.

изображение глюка изображение глюка

сведения о пакете библиотеки разработки:

$ dpkg --list | egrep 'lib(cairo|gtk).*-dev'
ii  libcairo2-dev:amd64                                1.15.10-2ubuntu0.1                           amd64        Development files for the Cairo 2D graphics library
ii  libgtk-3-dev:amd64                                 3.22.30-1ubuntu3                             amd64        development files for the GTK+ library

сведения о метаинформации библиотеки:

$ pkg-config --modversion gtk+-3.0 glib-2.0 gdk-pixbuf-2.0 cairo
3.22.30
2.58.1
2.36.11
1.15.10

х11 детали:

$ xdpyinfo | head -n 5
name of display:    :0
version number:    11.0
vendor string:    The X.Org Foundation
vendor release number:    12001000
X.Org version: 1.20.1

Сведения о Linux (на самом деле Zorin OS 15, а не Ubuntu 18.04):

$ uname -a
Linux <hostname> 4.18.0-25-generic #26~18.04.1-Ubuntu SMP Thu Jun 27 07:28:31 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

редактировать: Вот еще один очень интересный скриншот проблемы.

изображение глюка


person Void Star    schedule 04.07.2019    source источник


Ответы (1)


Во-первых, вызов gtk_widget_queue_draw в главном окне не имеет смысла, так как только GtkDrawingArea нужно обновлять с высокой частотой. Перерисовка элементов управления, с которыми вы не взаимодействовали, добавляет бесполезные накладные расходы.

Далее, я не вижу в вашем пользовательском интерфейсе ничего, что требовало бы постоянной перерисовки области рисования. Вы должны перерисовывать в ответ на события: значение параметра, которое изменилось на левой панели, или пользователь, щелкающий в области рисования, чтобы изменить точку обзора (если это то, что вы поддерживаете). Вы можете запустить тайм-аут в ответ на изменения управления и повторно инициализировать его при изменении другого, чтобы у пользователя было полсекунды, чтобы изменить все нужные настройки, а затем отобразить окончательный результат вместо промежуточных изменений. Это может быть полезно, когда у вас есть элементы управления со значением, которое может быстро меняться, например, используемые вами GtkSpinButton.

Судя по вашему тестированию, если вызов кода отрисовки каждую секунду, а не каждые 50 мс, дает вам такой результат, то проблема, скорее всего, в вашем коде отрисовки, а не в том, как GTK+ отрисовывает его. Чтобы убедиться, что это так, вы можете отключить источник тайм-аута, который перерисовывается, и добавить кнопку, которая при нажатии запускает одиночную перерисовку. Таким образом, вся проблема с частотой выходит за рамки уравнения, и у вас все еще должны быть эти ошибки рендеринга.

Следующий шаг — показать нам код в обработчике сигнала draw, так как ошибка, вероятно, кроется там. Если вы хотите отладить его, вы, вероятно, можете разделить рисунок, чтобы сохранить поверхность cairo, которую вы рисуете, в файле после того, как каждый столбец гистограммы будет нарисован. Затем, просматривая изменения изображение за изображением с помощью графического редактора, вы увидите, на каком изображении возникает проблема. С помощью некоторой регистрации вы увидите, какие значения вызывают проблему.

person liberforce    schedule 04.07.2019
comment
Согласен, перерисовывать весь GtkWindow смысла нет. Однако если я поставлю в очередь перерисовку для GtkDrawingArea, моя графика просто исчезнет. Я понимаю, что рисование обычно управляется событиями, но имейте в виду, что это анимация. Я регулярно ставлю в очередь розыгрыш, чтобы оживить свою графику. Предложения приветствуются. Поскольку высота столбцов жестко запрограммирована, код каждый раз выполняет одну и ту же последовательность вызовов Cairo, и все же эти сбои иногда появляются случайным образом только для одного кадра. - person Void Star; 04.07.2019
comment
Графика не должна исчезать при перерисовке GtkDrawingArea. Он должен просто запланировать вызов вашего обратного вызова, обрабатывающего сигнал draw из цикла событий, который будет рисовать вашу сцену. Если ваши жестко закодированные значения вызывают ошибку, то, как я уже сказал, сохраните промежуточные изображения в файл и убедитесь, что виноват конвейер рисования, а не дисплей. - person liberforce; 08.07.2019
comment
Я еще не дошел до этого эксперимента, но я попытался установить gtk_widget_set_double_buffered FALSE в своей области рисования, теперь поведение очень странное. Если я ставлю рисование в очередь в окне, происходит нормальное глючное поведение. Если я ставлю рисование в очередь в области рисования, происходит сбой, но есть два графика, наложенные друг на друга и немного смещенные по горизонтали. Я подозреваю, что сбой может быть связан с тем, что обработчик отрисовки вызывается дважды одновременно в разных потоках. Но с чего бы это? Почему это как-то смещено? Ничего из этого не имеет особого смысла. - person Void Star; 09.07.2019
comment
Кроме того, график смещения в фоновом режиме исчезает, когда я изменяю размер окна, и появляется снова, когда я прекращаю изменять размер, но сбои продолжаются, несмотря ни на что. Возможно, это две отдельные проблемы. - person Void Star; 09.07.2019
comment
Вы используете треды? GTK использует цикл событий, поэтому, если вы не создаете свои собственные потоки, вы не можете прервать отрисовку. - person liberforce; 09.07.2019
comment
Я нет, и то, что вы говорите, имеет смысл. Прошлой ночью я попробовал свою программу (без изменений) на другом компьютере под управлением Ubuntu 18.04. Глюков не было. Но когда я изменил код, чтобы поставить рисование в очередь в области рисования, а не в окне, он исчез, как обычно. Это, безусловно, отдельные вопросы. Интересно, у меня просто баг ОС, но если да, то почему он не влияет на другие приложения? - person Void Star; 10.07.2019
comment
Извините, но без кода мы мало что можем здесь сделать... Опубликуйте MCVE для получения дополнительной информации. совет. - person liberforce; 11.07.2019
comment
Я согласен, у меня еще не было времени. - person Void Star; 11.07.2019
comment
Вы также можете поделиться ссылкой на свою работу, если она использует бесплатную лицензию и находится в общедоступном репозитории. MCVE лучше, но, по крайней мере, это даст пищу для размышлений. - person liberforce; 12.07.2019