Оптимизация GUI на Qt +16


Как правило, при создании desktop-приложений на платформе Qt не возникает проблем, связанных с медленностью работы GUI. Qt – платформа достаточно надежная, неплохо вылизанная по всем параметрам, в том числе и по скорости работы. Однако всё же иногда бывают ситуации, когда из-за обилия виджетов графический интерфейс немного притормаживает, и это печально). В этой статье я приведу один частный пример простого графического интерфейса и покажу, как за два шага можно сначала ускорить его в 11 раз, а потом и в целых 34 раза. Вдобавок к этому, я постараюсь немного осветить механизм принятия решения для таких оптимизационных задач, постараюсь показать направление мыслей для правильного решения. Поехали!

Сразу оговорюсь, я не буду пытаться показать самый оптимальный код и самое быстрое решение. Я буду показывать лишь то решение, которое оказывается достаточным в плане скорости, и которое требует довольно небольшой переделки кода.

Итак, задачу в студию!

Нам надо нарисовать в две колонки список параметров: имя и значение. Параметров много. Они могут быть сгруппированы в группы с общим заголовком. Таким образом, у нас будет виджет с небольшой шириной, но с большой высотой. Его, конечно, стоит поместить в QScrollArea. Собственно, вот этот виджет:

Код виджета был написан когда-то давно, быстро и просто, и он содержал в себе намек на будущее расширение функциональности, то есть, говоря по-честному, некоторую функциональную избыточность. Вот этот код.

Файл FormLayoutWgt.h
#pragma once

#include <QScrollArea>
#include <QFormLayout>

class FormLayoutWgt : public QScrollArea
{
	Q_OBJECT
public:
	FormLayoutWgt(QWidget* parent = 0);
	virtual ~FormLayoutWgt();

	typedef QList< QPair<QString, QWidget*> > WidgetList;

	void setContents(const WidgetList& widgetList);

	QSize sizeHint() const;

public slots:
	void clear();

private:
	QFormLayout* _pLayout;
};

Файл FormLayoutWgt.cpp
#include "FormLayoutWgt.h"

FormLayoutWgt::FormLayoutWgt(QWidget* parent)
	: QScrollArea(parent)
{
	QWidget* pWidget = new QWidget;
	setWidget(pWidget);
	setWidgetResizable(true);

	_pLayout = new QFormLayout;
	_pLayout->setLabelAlignment(Qt::AlignLeft);
	_pLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
	pWidget->setLayout(_pLayout);
}

FormLayoutWgt::~FormLayoutWgt()
{
	clear();
}

void FormLayoutWgt::clear()
{
	if (_pLayout != 0)
	{
		QLayoutItem* item;
		for (int i = 0; i < _pLayout->rowCount(); i++)
		{
			item = _pLayout->itemAt(i, QFormLayout::LabelRole);
			if (item != 0) delete item->widget();

			item = _pLayout->itemAt(i, QFormLayout::FieldRole);
			if (item != 0) delete item->widget();
		}
		int count = _pLayout->rowCount();
		for (int i = 0; i < count; i++)
			_pLayout->removeRow(0);
	}
}

void FormLayoutWgt::setContents(const WidgetList& widgetList)
{
	for (int i = 0; i < widgetList.size(); i++)
	{
		if (widgetList.at(i).first.isEmpty())
			_pLayout->addRow(widgetList.at(i).second);
		else
			_pLayout->addRow(widgetList.at(i).first, widgetList.at(i).second);
	}
}

QSize FormLayoutWgt::sizeHint() const
{
	return QSize(270, 200);
}

А вот примерный сценарий использования такого виджета.

Файл MainWindow.h
#pragma once

#include "FormLayoutWgt.h"

class MainWindow : public QWidget
{
	Q_OBJECT

public:
	MainWindow(QWidget *parent = Q_NULLPTR);

public slots:
	void fill();

private:
	FormLayoutWgt::WidgetList generateContents() const;
	FormLayoutWgt* _flw;
};

Файл MainWindow.cpp
#include "MainWindow.h"

#include <QVBoxLayout>
#include <QPushButton>

MainWindow::MainWindow(QWidget *parent)
	: QWidget(parent)
{
	QVBoxLayout* layout = new QVBoxLayout();
	setLayout(layout);

	_flw = new FormLayoutWgt();

	QPushButton* fill = new QPushButton("Fill");
	QPushButton* clear = new QPushButton("Clear");

	layout->addWidget(fill);
	layout->addWidget(clear);
	layout->addWidget(_flw);

	connect(clear, SIGNAL(released()), _flw, SLOT(clear()));
	connect(fill, SIGNAL(released()), SLOT(fill()));
}

FormLayoutWgt::WidgetList MainWindow::generateContents() const
{
	FormLayoutWgt::WidgetList widgetList;

	for (int i = 0; i < 300; i++)
	{
		widgetList << qMakePair(QString(), new QLabel(QString("<H3>Group%1</H3>").arg(i + 1)));
		widgetList << qMakePair(QString("Field1"), new QLabel("Value1"));
		widgetList << qMakePair(QString("Field2"), new QLabel("Value2 long long long long"));
		widgetList << qMakePair(QString("Field3\n"), new QLabel("Value3 \n two rows"));
		widgetList << qMakePair(QString(), new QLabel("==========================="));
	}

	return widgetList;
}

void MainWindow::fill()
{
	auto content = generateContents();
	_flw->setContents(content);
}

Получили вот такое тестовое мини-приложение, которое и хотим ускорить:

Код виджета был написан когда-то давно, когда число параметров было небольшое – до нескольких десятков. И он исправно и быстро работал, но до тех пор, пока число параметров не возросло до нескольких сотен (или даже тысяч). GUI стало визуально притормаживать. Общее впечатление пользователя от мгновенно работающего приложения стало немного смазываться. И, в принципе, не стоит винить в этом старый код, ведь он писался для априори более простой задачи.

Итак, перед нами замаячила задача оптимизации. Как ее решать? Для начала, конечно, найти слабое место. И вот тут первая проблема: профилировщик нам тут особо не поможет. Казалось бы, что создание и добавление виджетов – и есть то слабое место. То есть вот эта часть кода:

_flw->setContents(content);

Но нет, измерение времени показывает тут какие-то жалкие 7 мс. А своими глазами мы видим, что GUI замирает почти на секунду. То есть события (мыши, клавиатуры) перестают обрабатываться на это время. И да, профилировщик показывает, что именно в цикле обработки событий и идут те самые непонятные вычисления.

В чем же здесь дело? Дело в самом Qt. А именно, в сложных алгоритмах ядра работы с виджетами. Дело в том, что, вызывая какую-либо команду в Qt, связанную с виджетами, Вы не можете никогда надеяться на то, что эта команда будет исполнена прямо сейчас. Часто она просто ставится в очередь задач. А исполнение задач из этой очереди может происходить как в отдельном потоке, так и в цикле обработки событий в основном потоке. Это, на самом деле, очень хорошая особенность Qt, благодаря которой мы получаем в целом очень быстрый GUI. Так, вызывая функцию QLabel::setText тысячу раз подряд с одним и тем же параметром, мы не получаем тысячекратного замедления, а получаем лишь однократную перерисовку QLabel с последним поданным значением параметра.

Конкретно в нашей задаче эта особенность Qt повлияла лишь на то, что стало сложнее понимать, как замерить наши тормоза. Что ж, придется немного поиграть с бубном. Для этого добавим таймер, запустим его после добавления всех данных на лэйаут, а остановим при первом вызове события resizeEvent.

void FormLayoutWgt::setContents(const WidgetList& widgetList)
{
	for (int i = 0; i < widgetList.size(); i++)
	{
		if (widgetList.at(i).first.isEmpty())
			_pLayout->addRow(widgetList.at(i).second);
		else
			_pLayout->addRow(widgetList.at(i).first, widgetList.at(i).second);
	}
	_timer.start();
}

void FormLayoutWgt::resizeEvent(QResizeEvent* event)
{
	if (_timer.isValid())
	{
		qDebug() << "gui time = " << _timer.elapsed();
		_timer.invalidate();
	}
	QScrollArea::resizeEvent(event);
}

Получили 703 мс, и это как раз то замедление, которое видят наши глаза, и которое послужило причиной оптимизации.

Что дальше? Как ускорить-то, если вся работа фактически происходит в затаенных глубинах Qt, в которые у нас доступа нет? (Да, если кто-то думает, что можно взять исходники Qt и немного подрихтовать, то эта плохая идея, но зато лучший способ убить пару месяцев.)

На самом деле все просто. Просто понять, почему такой долгий расчет идет в глубине кода Qt. Мы пытаемся разместить виджеты на QFormLayout. То есть, фактически, на табличном лэйауте. А ему для отрисовки каждого виджета нужно знать координаты, на которых рисовать. И также ширину и высоту. А значит, нужно опросить каждый виджет, в каких размерах ему будет комфортно отрисоваться. То есть как минимум, обратиться к функции sizeHint() для каждого виджета. А ведь есть еще функция QWidget::heightForWidth(int). А еще у виджетов могут быть разные политики QSizePolicy… И это все надо лэйауту учесть до отрисовки, потом провести расчет координат всех строк и столбцов, и только потом можно рисоваться. Потому неудивительно, что такой расчет для нескольких сотен виджетов может быть не такой мгновенный, к которому мы привыкли.

Хорошо, это мы поняли, а что с этим делать? Можно попытаться по сути сделать то же самое, что делает QFormLayout, но сэкономить при этом на обёртках. Получим немного больше кода, но более быстрого. За базу для отрисовки будем брать QTableWidget (ну раз мы поняли, что мы фактически таблицу и рисуем). Получим такой код (в нем сразу привожу танцы с бубном по поводу замера времени):

Файл TwoColumnWgt.h
#pragma once

#include <QTableWidget>
#include <QElapsedTimer>

class TwoColumnWgt : public QTableWidget
{
public:
	TwoColumnWgt(QWidget* parent = 0);
	virtual ~TwoColumnWgt();

	typedef QList< QPair<QString, QWidget*> > WidgetList;

	void setContents(const WidgetList& widgetList);
	void clear();

protected:
	virtual void paintEvent(QPaintEvent* event);

private:
	QElapsedTimer _timer;
};

Файл TwoColumnWgt.cpp
#include "TwoColumnWgt.h"

#include <QHeaderView>
#include <QTime>
#include <QDebug>

TwoColumnWgt::TwoColumnWgt(QWidget* parent)
	: QTableWidget(parent)
{
	setColumnCount(2);
	verticalHeader()->hide();
	horizontalHeader()->hide();
	setShowGrid(false);
	setWordWrap(false);
	setSortingEnabled(false);
	setSelectionMode(QAbstractItemView::NoSelection);
	setFocusPolicy(Qt::NoFocus);
	setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
	setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
	verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
	verticalHeader()->setDefaultSectionSize(QFontMetrics(this->font()).height());

	auto pal = palette();
	pal.setColor(QPalette::Base, pal.color(QPalette::Window));
	setPalette(pal);
}

TwoColumnWgt::~TwoColumnWgt()
{
	clear();
}

void TwoColumnWgt::clear()
{
	for (int i = rowCount() - 1; i >= 0; i--)
		model()->removeRow(i);
}

void TwoColumnWgt::setContents(const WidgetList& widgetList)
{
	int maxW1 = 0;
	int maxW2 = 0;
	int maxSpanW = 0;
	QFontMetrics fm(this->font());
	int pixelsHigh = fm.height();
	int hOffset = pixelsHigh / 2;

	setRowCount(widgetList.size());

	for (int row = 0; row < widgetList.size(); row++)
	{
		verticalHeader()->setSectionResizeMode(row, QHeaderView::Fixed);
		QWidget* w = widgetList.at(row).second;
		QString title = widgetList.at(row).first;
		if (title.isEmpty())
		{
			setSpan(row, 0, 1, 2);
			setCellWidget(row, 0, w);

			QSize sh = w->sizeHint();
			verticalHeader()->resizeSection(row, sh.height() + hOffset);
			maxSpanW = std::max<int>(sh.width(), maxSpanW);
		}
		else
		{
			int pixelsWide = fm.horizontalAdvance(title);
			maxW1 = std::max<int>(pixelsWide, maxW1);

			setItem(row, 0, new QTableWidgetItem(title));
			setCellWidget(row, 1, w);

			QSize sh = w->sizeHint();
			verticalHeader()->resizeSection(row, sh.height() + hOffset);
			maxW2 = std::max<int>(sh.width(), maxW2);
		}
	}

	int wOffset = fm.horizontalAdvance("123");
	maxW1 += wOffset;
	maxW2 = std::max<int>(maxSpanW - maxW1, maxW2);
	horizontalHeader()->resizeSection(0, maxW1);
	horizontalHeader()->resizeSection(1, maxW2);
	_timer.start();
}

void TwoColumnWgt::paintEvent(QPaintEvent* event)
{
	if (_timer.isValid())
	{
		qDebug() << "gui time = " << _timer.elapsed();
		_timer.invalidate();
	}
	QTableWidget::paintEvent(event);
}

Этот код уже показывает выполнение в 65 мс против старых 710. Ускорение в 11 раз. Причем, вся работа уже делается именно в коде TwoColumnWgt::setContents, а не в цикле обработки событий Qt. По крайней мере, теперь можно отказаться от трюков при замерах времени.

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

Как можно ускорить еще? Да просто двигаться в этом же направлении. Мы уже перешли от QFormLayout с ячейками-виджетами к QTableWidget с ячейками-виджетами. Осталось совсем избавиться от виджетов внутри QTableWidget, заменить их на более легковесные (и привычные таблицам) ячейки QTableWidgetItem. При этом, как может показаться, мы потеряем в функциональности, потому что раньше мы же могли ставить ЛЮБЫЕ виджеты в таблицу, а теперь только текст! На самом деле, и раньше не требовались ЛЮБЫЕ виджеты, раз уж мы говорим про конкретную задачу с двумя колонками «имя-значение». Нужен так или иначе текст, а значит, в QTableWidgetItem мы вписываемся. Но если всё же действительно требуется нарисовать что-то эдакое, то тогда нужно будет переносить это на делегаты, и это тоже будет довольно быстро работать.

К этой мысли – что от виджетов в таблице надо отказываться – можно прийти и другим путем. Запустив профилировщик, мы увидим, что критическое место – замеры размеров виджета (sizeHint). То есть надо заменить эти замеры на какие-то другие, более простые и быстрые. А это могут быть только функции класса QFontMetrics.

Итак, с этим пониманием упростим тогда систему входных
параметров и изменим немного код. Также приблизим конечную визуализацию
(размеры шрифтов, отступы) к начальной. Получим вот это:

Файл tcwvar.h
#pragma once

#include <QString>
#include <variant>

struct TcwContent
{
	QString title;
	QString value;
};

struct TcwHeader
{
	QString title;
};

struct TcwSeparator
{
};

using TcwVariant = std::variant<TcwContent, TcwHeader, TcwSeparator>;

Файл TwoColumnWgt.h
#pragma once

#include "tcwvar.h"

#include <QTableWidget>

class TwoColumnWgt : public QTableWidget
{
public:
	TwoColumnWgt(QWidget* parent = 0);

	void setContents(const std::vector<TcwVariant>& variants);
	void clear();

private:
	QString separatorText(const std::vector<TcwVariant>&) const;
};

Файл TwoColumnWgt.cpp
#include "tcw.h"

#include <QHeaderView>
#include <QFontMetrics>

TwoColumnWgt::TwoColumnWgt(QWidget* parent)
	: QTableWidget(parent)
{
	setColumnCount(2);
	verticalHeader()->hide();
	horizontalHeader()->hide();
	setShowGrid(false);
	setWordWrap(false);
	setSortingEnabled(false);
	setSelectionMode(QAbstractItemView::NoSelection);
	setFocusPolicy(Qt::NoFocus);
	setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
	setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
	verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
	verticalHeader()->setDefaultSectionSize(QFontMetrics(this->font()).height());

	auto pal = palette();
	pal.setColor(QPalette::Base, pal.color(QPalette::Window));
	setPalette(pal);
}

void TwoColumnWgt::clear()
{
	for (int i = rowCount() - 1; i >= 0; i--)
		model()->removeRow(i);
}

void TwoColumnWgt::setContents(const std::vector<TcwVariant>& variants)
{
	QFontMetrics fm(this->font());
	int		maxW1 = 0;
	int		maxW2 = 0;
	int		maxSpanW = 0;
	int		pixelsHigh = fm.height();
	int		hOffset = pixelsHigh / 2;
	QString	sepText = separatorText(variants);

	setRowCount(variants.size());

	for (int row = 0; row < variants.size(); row++)
	{
		if (const TcwContent* content = std::get_if<TcwContent>(&variants[row]))
		{
			// add left margin
			QString title = " " + content->title;

			// add two cells
			setItem(row, 0, new QTableWidgetItem(title));

			auto* item = new QTableWidgetItem;
			item->setData(0, content->value);
			setItem(row, 1, item);

			// calculate sizes
			int pixelsWide = fm.horizontalAdvance(title);
			maxW1 = std::max<int>(pixelsWide, maxW1);

			QFontMetrics fm(item->font());
			auto rect = fm.boundingRect(QRect(0, 0, 1000, 1000),
				Qt::AlignTop | Qt::AlignLeft, content->value);
			maxW2 = std::max<int>(rect.width(), maxW2);

			// correct row height
			verticalHeader()->resizeSection(row, rect.height() + hOffset);
		}
		else if (const TcwHeader* header = std::get_if<TcwHeader>(&variants[row]))
		{
			// add left margin
			QString title = " " + header->title;

			// add cell
			setSpan(row, 0, 1, 2);

			auto* item = new QTableWidgetItem;
			auto font = item->font();
			font.setBold(true);

			constexpr double fontRatio = 10.0 / 8.25;
			int pixelSize = font.pixelSize();
			double pointSizeF = font.pointSizeF();
			if (pointSizeF != -1)
				font.setPointSizeF(pointSizeF * fontRatio);
			else
				font.setPixelSize(qRound(pixelSize * fontRatio));

			item->setFont(font);
			item->setData(0, title);
			setItem(row, 0, item);

			// calculate sizes
			QFontMetrics fm(font);
			int hw = fm.horizontalAdvance(title) + fm.horizontalAdvance("1");
			maxSpanW = std::max<int>(hw, maxSpanW);
		}
		else if (const TcwSeparator* separator = std::get_if<TcwSeparator>(&variants[row]))
		{
			// add cell
			setSpan(row, 0, 1, 2);
			auto* item = new QTableWidgetItem;
			item->setData(0, sepText);
			setItem(row, 0, item);

			// correct row height
			verticalHeader()->resizeSection(row, hOffset);
		}
	}

	//correct column sizes
	maxW1 += fm.horizontalAdvance("12");
	horizontalHeader()->resizeSection(0, maxW1);
	if (maxW2 > maxSpanW - maxW1)
		resizeColumnToContents(1);
	else
		horizontalHeader()->resizeSection(1, maxSpanW - maxW1);
}

QString TwoColumnWgt::separatorText(const std::vector<TcwVariant>& variants) const
{
	// calculate column sizes
	int column1Size = 0;
	int column2Size = 0;
	for (auto& variant : variants)
	{
		if (auto* content = std::get_if<TcwContent>(&variant))
		{
			column1Size = std::max<int>(content->title.size(), column1Size);
			auto list = content->value.split('\n');
			for (auto item : list)
				column2Size = std::max<int>(item.size(), column2Size);
		}
	}

	// add left margin
	return " " + QString(column1Size + column2Size, '-');
}

Файл MainWindow.cpp (фрагмент)
std::vector<TcwVariant> MainWindow4::generate() const
{
	std::vector<TcwVariant> results;

	for (int i = 0; i < 300; i++)
	{
		results.emplace_back(TcwHeader{ QString("Group%1").arg(i + 1) });
		results.emplace_back(TcwContent{ "Field1", "Value1" });
		results.emplace_back(TcwContent{ "Field2", "Value2 long long long long" });
		results.emplace_back(TcwContent{ "Field3\n", "Value3 \n two rows" });
		results.emplace_back(TcwSeparator());
	}

	return results;
}

Это решение работает уже 21 мс, то есть в 34 раза быстрее, чем исходное решение.

Итог

Оптимизационные задачи, связанные с GUI, на Qt возникают не так уж часто, поскольку платформа Qt сама по себе очень неплоха (я не говорю про задачи типа «нарисовать график из миллиарда точек», там, конечно, нужно писать свою быструю библиотеку, как это сделали мы). Но, когда такие задачи возникают на обычном офисном GUI (набор простых контролов и форм), их всё же можно решать относительно несложными действиями и с весьма приятным результатом по скорости. Однако, как правило, всё же чем-то (немного) придется жертвовать, ибо чудес не бывает. Благо, что в архитектуре и в требованиях к софту, скорее всего, заложена некоторая избыточность, и вот ей как раз и можно пожертвовать.




Комментарии (19):

  1. eao197
    /#24465092

    Можно вопрос по поводу корректности освобождения памяти в первоначальной реализации?

    Если я правильно помню Qt, то там родительский QObject автоматически удаляет дочерние QObject-ы, поэтому если при создании QFormLayout в конструктор передать указатель на родителя, то об удалении QFormLayout можно не беспокоится. Но в показанном примере создание выглядит так:

    	_pLayout = new QFormLayout; // В конструктор ничего не передается.

    А в методе очистки clear удаления _pLayout нет вообще. Т.е. происходит утечка памяти.

    Если я понимаю правильно, то в clear нужно добавить `delete _pLayout`.

    Или я чего-то не знаю и здесь все OK?

    • BeardedBeaver
      /#24465136 / +3

      Там ниже по коду этот лейаут присваивается в виджет, если я правильно помню, именно в этот момент виджет становится родителем лейаута

  2. mctMaks
    /#24465728

    У графического приложения на Qt (5 версия) есть одна особенность: при изменении положения окна\его размеров обработка любых событий приостанавливается. Явного перехвата событий не делаю, работают только стандартные слоты.

    По ощущениям, "зависание" происходит в цикле обработки событий, но как это обойти не совсем ясно. Форма создается стандартными средствами Qt. Может подскажите в какую сторону посмотреть? Тоже ведь своего рода задача оптимизации)

    • Hvorovk
      /#24468230

      Разве обработка в не GUI потоке различных данных не решит проблему? Ну да, гуй будет все так же висеть, но различные процессы будут работать.

      • mctMaks
        /#24468458

        скорее всего решит, но тогда меняется сложившаяся система сигналов-слотов. не критично.

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

  3. unC0Rr
    /#24466232

    Не лучше ли было бы переделать отображение на использование единственного QLabel с использованием RichText?

    • kalirgum
      /#24467262 / +1

      У Вас правильное направление мыслей. Этот шаг оптимизации вполне может быть следующим. Но мы пробовали вгонять в QLabel html-таблицу, и это было медленно.

  4. da-nie
    /#24466582

    Не по этой теме, но всё-таки связано с GUI. Есть ли в Qt возможность отрисовки виджета (QPainter через drawPixmap) с синхронизацией с кадровой развёрткой? А то при частом рисовании получается расслоение экрана, когда вывод не попадает в развёртку, а картинка изменилась.

    • remova
      /#24467032

      а Вы случайно картинку не через RDP смотрите?

      • da-nie
        /#24467094 / +1

        Нет, я смотрю прямо на ЖК-мониторе на компьютере, где запускаю. И я вижу как картинка расслаивается. Если я использую для вывода картинки Direct Draw, то при включённой в нём синхронизации картинка выводится чётко. Но вот если я использую Qt ( не Open GL в Qt, а через drawPixmap), то виден рассинхрон частей экрана. Причём, что в Linux, что в Windows. Речь идёт про самопальную игру. Вот для Windows под Qt, а вот для Linux под Qt. А вот для Windows под Direct Draw. Во время движения у меня, как я уже сказал, в версиях для Qt экран расслаивается. И решения этой проблемы я не нахожу.

        • sgusev
          /#24468956 / +1

          Я собрал вашу игру, у меня картинка не расслаивается, как будто бы всё отрисовывается нормально. У меня правда макось и Qt 5.15, но не вижу, как бы это могло повлиять.

          • da-nie
            /#24469184

            А вы во весь экран сделали? Там надо внимательно смотреть когда идёт скроллинг фона (просто непрерывно идите). Тогда проскакивает расслоение — видно, что отрисовка не попадает в развёртку кадра.

  5. Zifix
    /#24467654 / +3

    А можно было не изобретать велосипед, и сразу использовать QTableView с моделью, наверняка получилось бы ещё гораздо быстрее, возможно, что на порядок.

    • zhekaal
      /#24469536

      По-моему тоже проблема в том, что не надо отрисовывать слишком много виджетов одновременно. Стоит применить подход view-model

      • remova
        /#24469878 / +1

        Так слишком много виджетов и не рисуется. Понятия "отрисовка" и "подготовка к отрисовке" - разные понятия. Отрисовка в Qt, в офисной графике, и в частности, в классе QTableWidget мгновенна (ну, пара миллисекунд). А вот подготовка - это да, это как раз про расчет координат и размеров всех ячеек. Без подготовки, например, скроллеры рисоваться не могут. И эта подготовка делается (должна делаться) при любом раскладе, что бы ни использовали - лайоут или таблицу в самых разных ее проявлениях (например, html внутри QLabel). А любой табличный виджет (QTableWidget, QTableView) - это и есть непосредственно MVC, модель там внутри. Просто в QTableWidget есть, само собой, свои небольшие обертки над базовыми классами, и вот на них потенциально можно сэкономить. Но маловероятно, что там получится что-то выжать, ибо очень уж простые там обертки, и все запросы к QTableWidget сразу идут в модель.

  6. DiegoRA
    /#24469332

    А можно поподробнее про библиотеку для отрисовки «графика из миллиарда точек»?
    Или она только для внутреннего использования и в оперсорс не будет выкладываться?

    • da-nie
      /#24469404

      А что там такого сложного? У меня мой компонент (он не для Qt, но и для Qt я его адаптировал) такое умеет. Но после каждого масштабирования будет задержка на пересчёт и объединение точек. В дальнейшем отрисовка идёт уже этих объединённых точек в данном разрешении. А можно заранее просчитать наборы с разной плотностью точек на пиксель (построить пирамиду, например, удваивая плотность каждый раз) и просто переключаться между ними — в этом случае задержка будет только в первую отрисовку.

    • kalirgum
      /#24469996

      Не будет выкладываться. По поводу мыслей - коллега в соседнем посте правильно подсказал то, что является самым началом этой библиотеки. А в ее развитии и в результате коммерческого использования родилось довольно много неочевидных на первый взгляд нюансов и особенностей. И довольно много кода было написано для достижения той самой оптимальности, да еще для того, чтобы подходить под разные сценарии использования.