Russian Qt Forum

Qt => 2D и 3D графика => Тема начата: DAMAL от Август 31, 2008, 15:15



Название: Как контролировать отрисовку в виджете?
Отправлено: DAMAL от Август 31, 2008, 15:15
Возникла довольно интересная ситуация, которую не получается решить:
имеется виджет QLabel, у которого переописан метод paintEvent(): (image - поле класса myQLabel)

Код:
void myQLabel::setNextFrame(unsigned char *bitmap)
{
    memcpy(image, bitmap, size);
    QCoreApplication::postEvent(this, new QEvent(QEvent::Paint));
}
void myQLabel::paintEvent(QPaintEvent *e)
{
    some_image_transformation(image);
    QPixmap pixmap = QPixmap::fromImage( image );
    QPalette palette;
    palette.setBrush(backgroundRole(), QBrush(pixmap));
    setPalette(palette);
}
Далее довольно интенсивно виджету посылаются события о перерисовке (область рисования большая, до 704х576 пикселов должна меняться до 25 раз в секунду). На быстром компьютере всё работает замечательно, но стоит запустить приложение на медленном компьютере - Qt начинает не успевать обрабатывать все события о перерисовке в рельном времени. И вот тут самое неприятное: вместо того, чтобы сбрасывать избыточные события, они начинают копиться где-то в недрах Qt. Через пару часов работы оказываются накопленными чуть ли не сотни неотрисованных вовремя кадров, которые выводятся на экран по мере их обработки.
Хочу поинтересоваться, если возможность контролировать процесс отрисовки? Безуспешно искал какой-нибудь счётчик очереди на отрисовку чтобы не посылать "лишних" кадров, ставил различные флаги виджету типа WNoAutoErase и NoBackground - безуспешно.
Ситуация наигрывается в Qt 3-ей и 4-ой версий.


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: SASA от Август 31, 2008, 16:32
Можно поставить окошку атрибут WA_PaintOnScreen, чтоб рисовалось быстрей.
Так же есть замечательные функции QCoreApplication::sendPostedEvents(), QCoreApplication::flush () и QCoreApplication::processEvents ().


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: DAMAL от Август 31, 2008, 17:25
Спасибо за идеи. Попробую метод flush(). С processEvents() часто имел дело, использовал его в ситуациях, когда не было возможности запустить автоматическую обработку сообщений командой exec().


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: pastor от Август 31, 2008, 17:55
Заинтересовала строка:

Код:
QCoreApplication::postEvent(this, new QEvent(QEvent::Paint));

Может вы лучше обратите внимание на repaint\update?


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: DAMAL от Август 31, 2008, 18:45
Заинтересовала строка:

Код:
QCoreApplication::postEvent(this, new QEvent(QEvent::Paint));

Может вы лучше обратите внимание на repaint\update?

Согласен, выглядит сложно и странно. Не стал полностью описывать ситуацию сразу, чтобы не загромождать сообщение. Дело в том, что окно для рисования создаётся у меня вне главного потока окна. Я работаю в Linux, и при использовании всех синхронных сообщений (repaint, update и т.д.) во внепоточное окно в моём случае иксы начинают генерировать ошибку "Acynchrone reply", единственным решением было посылать сингал о рисовании асинхронно - т.е. через postEvent.


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: ритт от Август 31, 2008, 20:43
по идее QWidget::update() делает то же самое


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: DAMAL от Август 31, 2008, 21:52
по идее QWidget::update() делает то же самое

Цитата для QWidget::update() из help: "The paint event is processed after the program has returned to the main event loop". А у меня виджет с рисованием в другом потоке, где нет main event loop. Очень может быть, что в винде действительно всё работает проще когда виджеты в разных потоках. А Linux к этому очень критично относится, иксы начинают пачками бросать в консоль ошибки и предупреждения по тексту которых вообще сложно понять об их причине.

Реализацию свою я, кстати, взял из одного учебника по Qt (не вспомню какого имено). Там был пример графического редактора. Чтобы не "замораживать" главное окно на момент выполнения операций по преобразованию обрабатываемых изображений - операции преобразования выполнялись в отдельном потоке, передача изображения из главного диалога в поток и обратно как раз выполнялась посылкой сигнала postEvent().


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: ритт от Август 31, 2008, 22:23
благодарю за экскурс в документацию )
Код:
void QWidget::update()
{
    if (updatesEnabled() && isVisible()) {
        if (testAttribute(Qt::WA_WState_InPaintEvent)) {
            QApplication::postEvent(this, new QUpdateLaterEvent(rect()));
//...
а QCoreApplication::postEvent(this, new QEvent(QEvent::Paint)) в каком потоке выполняется? )

и я всё же не понимаю: если вся отрисовка виджета выполняется в отдельном потоке, то как происходит синхронизация с основным потоком? - в коде выше ничего подобного нет


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: DAMAL от Август 31, 2008, 23:15
В главном потоке как и положено создаётся главное диалоговое окно. Во втором потоке создаётся объект класса myQLabel (см. первый пост в этой теме). В главном диалоговом окне имеется указатель на созданный объект myQLabel, по этому указателю главный диалог может вызывать метод myQLabel::setNextFrame(unsigned char *bitmap). Объект myQLabel при получении очередного буфера с изображением сам себе шлёт postEvent с событием о перерисовке.

Вижу, что QWidget::update() делает то же самое, но я первым делом пробовал такой вариант - иксы писали ту самую ошибку об асинхронности. Может для Linux QWidget::update() определён иначе?
На самом деле, пусть даже был бы в моём коде QWidget::update() вместо QCoreApplication::postEvent(this, new QEvent(QEvent::Paint)). Накопление событий в очереди это бы не отменило. Так или иначе, хочется контролировать очередь отрисовки. Допустим, по моей схеме рисуется график с ритмами сердца - на данный момент: пациент уже давно умер, а на экране ещё долго зигзаги вместо прямой линии. :-)


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: ритт от Сентябрь 01, 2008, 01:40
т.е. во втором потоке создаётся виджет, ложится на диалог, выполняющийся в первом потоке, а затем дёргаются события на перерисовку из первого потока во второй и результат - снова в первый? и всё это без механизмов синхронизации потоков?
мне кажется, что правильнее было бы оставить виджет там, где ему и место, подготавливать изображение во втором потоке, а по готовности через QueuedConnection слать сигнал, связанный со слотом update() виджета. дополнительно можно предусмотреть синхронизацию потоков, что может дать некоторую оптимизацию


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: DAMAL от Сентябрь 01, 2008, 07:48
Виджет во втором потоке не имеет видимых и логических связей с диалогом, он открывается в отдельном окне. Синхронизация выполняется на уровне защитой мьютексом буфера "image" во втором потоке. Готовить изображение можно только в первом потоке - там оно снимается с физического устройства.
А про "QueuedConnection" я почитаю, не пользовался пока этип классом!


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: ритт от Сентябрь 01, 2008, 08:15
QueuedConnection - это значение опционального параметра QObject::connect :)

если синхронизацию потоков выполняешь /* правильно */, то не вижу где могут накапливаться события и от чего может лагать
единственное что, QCoreApplication::postEvent(this, new QEvent(QEvent::Paint)) ставит событие в очередь и если делать именно так, то чтобы события не скапливались в очереди, перед каждой постановкой очередь нужно очищать...а там могут находиться вовсе и не QEvent::Paint

ну, что могу ещё предложить? сделай два буффера - пока один заблокирован, заполняй второй; а потом наоборот (правда, не очень-то понял к чему тут memcpy(image, bitmap, size) - может это и есть "второй" буффер? в таком случае который из них локаешь?)


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: pastor от Сентябрь 01, 2008, 11:59
Во втором потоке создаётся объект класса myQLabel

Вот это уже интересно. Все гуевые элементы должны создаваться в гуевом (главном) потоке.

Цитировать
In GUI applications, the main thread is also called the GUI thread because it's the only thread that is allowed to perform GUI-related operations.

Although QObject is reentrant, the GUI classes, notably QWidget and all its subclasses, are not reentrant. They can only be used from the main thread. As noted earlier, QCoreApplication::exec() must also be called from that thread.



Название: Re: Как контролировать отрисовку в виджете?
Отправлено: DAMAL от Сентябрь 01, 2008, 13:01
ну, что могу ещё предложить? сделай два буффера - пока один заблокирован, заполняй второй; а потом наоборот (правда, не очень-то понял к чему тут memcpy(image, bitmap, size) - может это и есть "второй" буффер? в таком случае который из них локаешь?)

image - буфер класса myQLabel, он подлежит лочению, ведь в момент вызова setNextFrame() он может читаться в методе paintEvent(). Копирование необходимо потому как при выходе из setNextFrame() входной буфер bitmap будет удалён.
Причины накопления во многом понятны. Qt при рисовании вроде как пытается искать изменённые от предыдущего кадра регионы и перерисовывать только их. А это вычислительные операции. Вот так и не смог "заставить" Qt сразу посылать кадры на отображение, в идеале бы ещё и через overlay (т.е. в память видеокарты).

По поводу виджета вне гуёвого потока - согласен, не очень по рекомендациям. Но ведь Qt не выдаёт критическую ошибку от такой архитектуры! Да и видел я что-то такое в примерах...


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: pastor от Сентябрь 01, 2008, 13:43
Но ведь Qt не выдаёт критическую ошибку от такой архитектуры! Да и видел я что-то такое в примерах...

Вам чесно сказать просто ПОКА везет с такой архитектурой. Обычно приложение валиться в очень неожиданых местах. А в каких примерах вы такое видели? Какую версию Qt используете?

И ещё... можно ли взглнянуть на код второго потока где создаеться экземпляр myQLabel?


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: DAMAL от Сентябрь 01, 2008, 17:50
Архитектура в двух словах выглядит так:

Код:
class ImageProcessor : public QThread
{
public:
    ImageProcessor();
    ~ImageProcessor();
private:
    myQLabel *frame_render;
    void run();
    bool dataReady;
};

void ImageProcessor::run()
{
    while( true ) {
        if(dataReady) {
            frame_render->setNextFrame( image_buffer );
        } else {
            Sleep(3);
        }
    }
}

class MainDialog : public QDialog
{
public:
    MainDialog();
    ~MainDialog();
};

int main(int argc, char *argv[])
{
    QApplication a( argc, argv );
    MainDialog *pDialog = new MainDialog();
    ImageProcessor *pProcessor = new ImageProcessor();
    a.exec();
    return 0;
}


Юзай тэги КОД плиз


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: pastor от Сентябрь 01, 2008, 17:58
Ага, понятно. Т.е. экземпляр myQLabel создаеться, как я понимаю, в контрукторе ImageProcessor (а это не есть другой поток!), а юзаеться в другом потоке (метод ImageProcessor::run), верно? И это есть ошибка, про которую я писал выше. Из другого потока к гую обращаться напрямую нельзя, нужно юзали либо ивенты либы сигнал\слот. Когда нибудь приложение вылетит в кору, а вы не будете понимать почему. Советую этот момент переделать.

Upd:

Мой вопрос можно сказать снят. Я посморел реализацию myQLabel::setNextFrame, и все стало понятно:

Код:
void myQLabel::setNextFrame(unsigned char *bitmap)
{
    memcpy(image, bitmap, size);
    QCoreApplication::postEvent(this, new QEvent(QEvent::Paint));
}

Т.к. метод myQLabel::setNextFrame вызываеться из другого потока именно для этого и нужена строка

Код:
QCoreApplication::postEvent(this, new QEvent(QEvent::Paint));

Но вопросов стало больше после кода:

Код:
int main(int argc, char *argv[])
{
    QApplication a( argc, argv );
    MainDialog *pDialog = new MainDialog();
    ImageProcessor *pProcessor = new ImageProcessor();
    a.exec();
    return 0;
}

Кто запускает поток ImageProcessor? Для чего нужен MainDialog если он не показан?


Название: Re: Как контролировать отрисовку в виджете?
Отправлено: DAMAL от Сентябрь 02, 2008, 21:11
Опишу практическое применение. Приложение представляет собой сетевой видео-плеер. Имеется главное окно (MainDialog), в котором задаются настройки и происходит инициация соединения с видео-сервером. После установления соединения создаётся объет класса ImageProcessor, принимающий видео-кадры по сети и отображающий их в видео-окне. Одновременно может быть создано несколько объектов класса ImageProcessor.
Вызов метода setNextFrame действительно происходит из другого потока, и только посылка события способом postEvent позволяет избежать асинхронных ошибок иксов.
К сожалению, мой старый компьютер, на котором наигрывалось накопление кадров, сгорел. Починю - сразу попробую методы flush() и sendPostedEvent(), очень надеюсь они помогут очищать очередь...