Russian Qt Forum

Qt => 2D и 3D графика => Тема начата: kamre от Сентябрь 10, 2009, 21:15



Название: [SOLVED]Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 10, 2009, 21:15
Изначально при запуске ничего особо не тормозит, т.е. можно передвигать при нажатой правой кнопке мыши, зумиться по колесику, и все будет плавно.

Но если призумиться на самую маленькую окружность, то все начинает заметно тормозить. Из за чего возникают тормоза? Как от них избавиться?

Widget.h:
Код:
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QList>

struct circle {
    QPointF c;
    qreal r;
    circle(QPointF center, qreal radius): c(center), r(radius) {}
};

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);

protected:
    void paintEvent(QPaintEvent *event);

    void mousePressEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);

    void wheelEvent(QWheelEvent *event);

private:
    QList<circle> circles;
    QPointF shift;
    qreal scale;
    QPoint previous;
    bool isDragging;
};

#endif // WIDGET_H

Widget.cpp:
Код:
#include "widget.h"

#include <math.h>
#include <QPainter>
#include <QMouseEvent>
#include <QWheelEvent>

circle tangent2(const circle &c1, const circle &c2, qreal r, const QPointF &p)
{
    qreal r1 = c1.r + r;
    qreal r2 = c2.r + r;
    qreal d = c1.r + c2.r;
    qreal dx = (d*d - r2*r2 + r1*r1)/(2*d);
    qreal dy = sqrt(r1*r1 - dx*dx);
    QPointF v = (c2.c - c1.c)/d;
    QPointF n(-v.y(), v.x());
    QPointF pv = p - c1.c;
    if (pv.x()*n.x() + pv.y()*n.y() < 0)
        n = -n;
    QPointF c = c1.c + dx*v + dy*n;
    return circle(c, r);
}

circle tangent3(const circle &c1, const circle &c2, const circle &c3)
{
    qreal k1 = 1/c1.r;
    qreal k2 = 1/c2.r;
    qreal k3 = 1/c3.r;
    qreal a = k1 + k2 + k3;
    qreal b = 2*sqrt(k1*k2 + k2*k3 + k3*k1);
    qreal k = qMax(qAbs(a - b), a + b);
    return tangent2(c1, c2, 1/k, c3.c);
}

Widget::Widget(QWidget *parent)
    : QWidget(parent), shift(300, 400), scale(30), isDragging(false)
{
    setAutoFillBackground(false);
    circle c1(QPointF(0, 0), 5);
    circle c2(QPointF(8, 0), 3);
    circle c3 = tangent2(c1, c2, 4, QPointF(0, -1));
    circles << c1 << c2 << c3;
    for (int i = 0; i < 6; ++i) {
        circle c = tangent3(c1, c2, c3);
        circles << c;
        c1 = c2;
        c2 = c3;
        c3 = c;
    }
}

void Widget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.fillRect(0, 0, width(), height(), Qt::darkGray);
    painter.setBrush(Qt::NoBrush);
    painter.setPen(QPen(QBrush(Qt::white), 1.5));
    painter.setRenderHint(QPainter::Antialiasing, true);
    foreach(const circle &c, circles)
        painter.drawEllipse(c.c * scale + shift, c.r * scale, c.r * scale);
}

void Widget::mousePressEvent(QMouseEvent *event)
{
    if (!isDragging && event->button() == Qt::RightButton) {
        isDragging = true;
        previous = event->pos();
    }
}

void Widget::mouseReleaseEvent(QMouseEvent *event)
{
    if (isDragging && event->button() == Qt::RightButton) {
        isDragging = false;
    }
}

void Widget::mouseMoveEvent(QMouseEvent *event)
{
    if (isDragging) {
        shift += event->pos() - previous;
        previous = event->pos();
        repaint();
    }
}

void Widget::wheelEvent(QWheelEvent *event)
{
    int numDegrees = event->delta() / 8;
    int numTicks = numDegrees / 15;
    qreal factor = pow(1.2, -numTicks);
    shift += (1 - factor) * (event->pos() - shift);
    scale *= factor;
    repaint();
}

main.cpp
Код:
#include <QtGui/QApplication>
#include "widget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.resize(800, 600);
    w.show();
    return a.exec();
}


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 12, 2009, 20:56
Хоть у кого-нибудь проблема с этим кодом воспроизводится?


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: ufna от Сентябрь 12, 2009, 21:06
ну, так то тут по-моему очевидно, что если ты отсечки никакой не делаешь, то эллипс у тебя рисуется каждый раз во всем объеме. Вот и представь какие у этого всего получаются объемы и размеры.

имхо, стоит сделать отсечку clip region'а, в самую первую очередь (см. demo кутяшное, где еще Артур Плагин юзается).


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 12, 2009, 22:57
имхо, стоит сделать отсечку clip region'а, в самую первую очередь (см. demo кутяшное, где еще Артур Плагин юзается).

Если добавить clipRect вот так:
Код:
void Widget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.fillRect(0, 0, width(), height(), Qt::darkGray);
    painter.setClipRect(0, 0, width(), height());  // <<<<<< added
    painter.setBrush(Qt::NoBrush);
    painter.setPen(QPen(QBrush(Qt::white), 1.5));
    painter.setRenderHint(QPainter::Antialiasing, true);
    foreach(const circle &c, circles)
        painter.drawEllipse(c.c * scale + shift, c.r * scale, c.r * scale);
}
то ничего не меняется и тормозит точно также. По идее такую "отсечку" должен делать сам QPainter, т.к. ему заранее известны размеры QWidget.

Какая-то другая "отсечка" имелась ввиду?


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: Igors от Сентябрь 13, 2009, 12:43
Хоть у кого-нибудь проблема с этим кодом воспроизводится?
У меня не воспроизводится, никаких тормозов не наблюдаю
Примечание: в следующий раз прикрепите zip'чик с файлами чтобы не выдирать их copy/paste


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: pastor от Сентябрь 13, 2009, 12:46
kamre, протестируй Valgrind'ом на предмет потери производительности


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 13, 2009, 13:02
У меня не воспроизводится, никаких тормозов не наблюдаю
Нужно призумиться так, чтобы самая маленькая окружность была почти на все окно. После этого появляются тормоза.


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: Igors от Сентябрь 13, 2009, 13:21
Нужно призумиться так, чтобы самая маленькая окружность была почти на все окно. После этого появляются тормоза.
Делал, не тормозит (платформа Mac)


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 13, 2009, 14:06
Делал, не тормозит (платформа Mac)
Я только на Windows/Linux пробовал, и на обоих тормозит.


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: pastor от Сентябрь 13, 2009, 14:56
Я только на Windows/Linux пробовал, и на обоих тормозит.


Повторюсь. Глянь профайлером где проблема


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 15, 2009, 07:13
Повторюсь. Глянь профайлером где проблема

Глянул в Linux с помощью valgrind.

Вот такой расклад когда нет зума и не тормозит:
(http://pic.ipicture.ru/uploads/090915/X5GC3Scz19.png)

А вот так получается, когда зум большой и появляются тормоза:
(http://pic.ipicture.ru/uploads/090915/DiRvE5X2Q2.png)

Т.е. получается, что проблема возникает при вызове QPainterPath::intersected.
И как мне теперь с этим бороться?


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 15, 2009, 07:24
Вот, кстати, тот же самый пример на Java. Ничего не тормозит, ни при каком зуме.
Неужели отрисовка в Qt получается тормознее чем в Java2D?


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: Rcus от Сентябрь 15, 2009, 09:17
Интересно... первые 10 минут рассматривания графов и тестов. Очень интересно после пересборки с отладочной версией Qt. И совсем неинтересно после чтения QOutlineMapper::endOutline() (Qt4.5.2 src/gui/painting/qoutlinemapper.cpp line: 257)


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 15, 2009, 09:45
И совсем неинтересно после чтения QOutlineMapper::endOutline() (Qt4.5.2 src/gui/painting/qoutlinemapper.cpp line: 257)
Вот такая константа:
Код:
// This limitations comes from qgrayraster.c. Any higher and
// rasterization of shapes will produce incorrect results.
const int QT_RASTER_COORD_LIMIT = 32767;
Получается, что растеризатор в Qt какой-то ограниченный что ли? Ведь в Java2D нет таких проблем с растеризацией.
А если получается больше по размерам, то делается clip, который ужасно тормозной? И как с этим бороться?



Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: Rcus от Сентябрь 15, 2009, 10:00
Ага, а можно назвать специалированным и заточенным под рисование виджетов :). Попробуйте лучше Graphics View Framework.


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 15, 2009, 10:12
Ага, а можно назвать специалированным и заточенным под рисование виджетов :).
Ну для растеризации под окошки размеры вроде вполне достаточные )
Но тогда не понятно почему при включении отсечения столько много времени начинает тратиться на самом этом отсечении. Ведь наверняка в Java2D не делают растеризацию всей окружности, когда ее радиус огромный, скорее всего только в видимом участке растеризация происходит. Кроме того странно еще то, что судя по второму скриншоту там пересечение кривых безье ищется, хотя по идее для отсечения окружности (эллипса) такого не должно происходить.

Попробуйте лучше Graphics View Framework.
А чем он здесь поможет? Тем более в нем не поддерживаются cosmetic pens with non zero width:
Цитировать
QGraphicsItem does not support use of cosmetic pens with a non-zero width.
А мне как раз хотелось бы рисовать линии с некоторой постоянной толщиной, не зависящей от уровня зума.


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: Tonal от Сентябрь 15, 2009, 10:22
На Win9x GDI 16-ти разрядный. И вроде на старых Маках.
Так что это, возможно артефакт ещё тех времён.

Кроме того, я как-то на Win9x столкнулся с тем, что некоторые функции начинали чудить уже при привышении 4*1024 в значении любой координаты...


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: SABROG от Сентябрь 15, 2009, 10:59
На Win9x GDI 16-ти разрядный. И вроде на старых Маках.
Так что это, возможно артефакт ещё тех времён.

Кроме того, я как-то на Win9x столкнулся с тем, что некоторые функции начинали чудить уже при привышении 4*1024 в значении любой координаты...

Проверил на WinXP, тормозит при scale factor выше ~5k. После этого если зумить дальше и пытаться перетаскивать, то программа просто может зависнуть на несколько секунд. При этом память не кушается.


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 15, 2009, 11:05
Проверил на WinXP, тормозит при scale factor выше ~5k. После этого если зумить дальше и пытаться перетаскивать, то программа просто может зависнуть на несколько секунд. При этом память не кушается.
Размеры bounding rect для всей конструкции как раз где-то 13x13, т.е. радиуса ~6. При scale factor больше ~5k как раз будет вылезать за указанный выше предел. После этого включается clip и начинаются жуткие тормоза. Как обойти эту проблему не понятно.


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: SABROG от Сентябрь 15, 2009, 12:27
Я немного модифицировал программу таким образом, чтобы сразу видно было в чем проблема. Достаточно двигать правой кнопкой мышки по окну и все лагает. Я приаттачил компилируемый пример.


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: Winstrol от Сентябрь 15, 2009, 12:47
Можно попытаться включить hardware ускорение, запустив приложение из коммандной строки -graphicssystem opengl


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: SABROG от Сентябрь 15, 2009, 12:50
Можно попытаться включить hardware ускорение, запустив приложение из коммандной строки -graphicssystem opengl
У меня это приводит к тому, что виджет становится белым и ничего не рисуется. А в консоль пишется такое:

Код:
PB Sample buffers: false
hijackWindow() context created for Widget(0x22ff18, name = "Widget") 1


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 15, 2009, 13:46
Заметил еще одно различие по сравнению с Java версией. Если после старта распахнуть окно на весь экран (у меня это 1680х1050) и начать активно таскать мышкой с зажатой правой кнопкой,
то Qt загружает полностью одно ядро процессора:
(http://pic.ipicture.ru/uploads/090915/y1xxBMy2S7.png)
а вот Java не больше 10-12%:
(http://pic.ipicture.ru/uploads/090915/E7VY4HTC1X.png)
правда памяти Java как всегда отхапала со старта около 30Мб.

Это нормальное поведение для Qt при отрисовке на большом widget? Или это можно как-то исправить?


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: pastor от Сентябрь 15, 2009, 14:40
kamre, попробуй на Qt 4.6 TP1 попробовать


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 16, 2009, 17:41
kamre, попробуй на Qt 4.6 TP1 попробовать
Обязательно попробую, уже скачал исходники осталось собрать и попробовать. Только скорее всего это не поможет, т.к. судя по исходникам здесь (http://qt.gitorious.org/qt/qt/blobs/4.6/src/gui/painting/qoutlinemapper.cpp#line234) и здесь (http://qt.gitorious.org/qt/qt/blobs/4.6/src/gui/painting/qpathclipper.cpp) в этой версии ничего особо не изменилось.

P.S. собралось под win32-msvc2008, никаких отличий нет, т.е. тормозит также.


Название: Re: Тормозит отрисовка окружностей большого радиуса
Отправлено: kamre от Сентябрь 17, 2009, 10:36
Похоже так просто сразу все рисовать через QPainter не получится в моем случае. Нужно будет самому делать clip под окошко. Например, вот так не тормозит уже ни при каком зуме:

Код
C++ (Qt)
void Widget::paintEvent(QPaintEvent *event)
{
   qreal penWidth = 1.5;
   QPainter painter(this);
   painter.fillRect(0, 0, width(), height(), Qt::darkGray);
   painter.setBrush(Qt::NoBrush);
   painter.setPen(QPen(QBrush(Qt::white), penWidth));
   painter.setRenderHint(QPainter::Antialiasing, true);
   QRectF clipRect(0, 0, width(), height());
   clipRect.adjust(-penWidth, -penWidth, penWidth, penWidth);
   QPainterPath clipPath;
   clipPath.addRect(clipRect);
   foreach(const circle &c, circles) {
       QPointF center = c.c * scale + shift;
       qreal radius = c.r * scale;
       QPainterPath path;
       path.addEllipse(center, radius, radius);
       path = path.intersected(clipPath);
       if (path != clipPath)
           painter.drawPath(path);
   }
}