Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Для новичков > Ускорение отрисовки графика


Автор: Beltar 14.10.2009, 12:19
Прошу прощения, что создаю тему в этом разделе, но раздел для графики мне сейчас просто недоступен из-за каких-то идиотизмов в настройке нашей корпоративной системы безопасности и экономии трафика, которая похоже принимает его за развлекательный сайт.

Суть такая, есть самодельный компонент для отрисовки графиков (ни TeeChart, ни другие бесплатные компоненты меня не устроили).
Порядок отрисовки такой:

Точек для отрисовки может быть несколько десятков тысяч. Наборы точек перед отрисовкой сортируются и не попадающие в диапазон по оси Х отсекаются. Все основное рисование:
Код

for j:=Seria.LeftX to Seria.RightX do
  begin
  P:=ValueToPoint(Seria.FPoints[j]);
  if ((PrevP.X=P.X) and (PrevP.Y=P.Y)) then Continue;
  Canvas.LineTo(P.X,P.Y);
  PrevP.X:=P.X;
  PrevP.Y:=P.Y;
  end;


Отрисовка всего этого дела при толщине линий трендов равной 2 занимает в худшем случае ~600 мс. Что меня не очень устраивает. Как показали замеры, почти все время уходит на Canvas.LineTo соединяющие точки трендов, отрисовка координатной сетки, прочих вспомогательных элементов, рассчеты требуют меньше 32 мс. Можно использовать единичную толщину линий, тогда тренд рисуется раз в 8-10 быстрее, но от возможности настройки отказываться не хочу.

Как можно ускорить? Мне пока в голову приходит только использовать DirectX.

Автор: Alexeis 14.10.2009, 12:37
Цитата(Beltar @  14.10.2009,  11:19 Найти цитируемый пост)
Как можно ускорить? Мне пока в голову приходит только использовать DirectX. 

  Если с дровами OpenGL нет проблем, то я бы посоветовал OpenGL. В нем есть функции работы с графикой 2D графикой. DirectX будет работа с 3D. Я сам так делал. Есть также возможности аппаратной отрисовки штриховых линий. Скорость намного выше. 

  Есть более простое решение. Использовать вместо LineTo Polyline и передвать сразу массив вершин. LineTo тормозит из-за издержек на начало рисования и окончание. Polyline производит всего одну операция начала и завершения поэтому скорость будет в n раз выше (где n число точек)

Автор: ~FoX~ 14.10.2009, 12:40
GDI+

Автор: Beltar 14.10.2009, 15:39
Цитата

 Есть более простое решение. Использовать вместо LineTo Polyline и передвать сразу массив вершин. LineTo тормозит из-за издержек на начало рисования и окончание. Polyline производит всего одну операция начала и завершения поэтому скорость будет в n раз выше (где n число точек)


Я рассуждая примерно так же, что вдруг хотя бы на вызовах сэкономлю, кинул на форму TPaintBox с парой кнопок:

Код

procedure TForm2.Button1Click(Sender: TObject);
var w,h,i:Integer;
    Canvas:TCanvas;
    t:Cardinal;
begin
w:=PB.Width;
h:=PB.Height;
Randomize;
Canvas:=PB.Canvas;
Canvas.Pen.Width:=2;
t:=GetTickCount;
for i:=0 to D do
  Canvas.LineTo(Random(w-1),Random(h-1));
Label1.Caption:=IntToStr(GetTickCount-t);
end;

procedure TForm2.Button2Click(Sender: TObject);
var w,h,i:Integer;
    Canvas:TCanvas;
    t:Cardinal;
begin
w:=PB.Width;
h:=PB.Height;
Randomize;
Canvas:=PB.Canvas;
Canvas.Pen.Width:=2;
for i:=0 to D do
  begin
  P[i].X:=Random(w-1);
  P[i].Y:=Random(h-1);
  end;
t:=GetTickCount;
Canvas.Polyline(P);
Label1.Caption:=IntToStr(GetTickCount-t);
end;


При D=999 polyline отрабатывала за 172 мс, а цикл с LineTo 32, при D=9999 polyline просто вешает компьютер так, что его потом только ресетом.  smile 

Цитата
  Если с дровами OpenGL нет проблем


Э-э. Вроде они с любыми драйверами видеокарт, даже чипсетками всегда идут?

Вообще с DirectX я пару лет назад ковырялся, даже книжечку купил небезызвестного Миши Фленова, так что в этом плане он немного предпочтительнее OpenGL, который я не ковырял вообще.

Цитата

GDI+


Качнул либы, немного покурил форум, как заставить их работать с 2007.

Код

Randomize;
t:=GetTickCount;
for i:=0 to 9999 do
  PB.Canvas.LineTo(Random(100), Random(100));
Label1.Caption:=IntToStr(GetTickCount-t);


И Label1.Caption=32

Код

Randomize;
GDIPlus := TGPGraphics.Create(PB.Canvas.Handle);
pen:= TGPPen.Create(MakeColor(255, 0, 0, 255));
t:=GetTickCount;
for i:=0 to 9999 do
  GDIPlus.DrawLine(pen, 0, 0, Random(100), Random(100));
GDIPlus.DrawRectangle(pen,20,20,200,200);
GDIPlus.Free;
pen.Free;
Label1.Caption:=IntToStr(GetTickCount-t);


И Label1.Cation=18266 smile Что я делаю не так??

Автор: Alexeis 14.10.2009, 16:25
Цитата(Beltar @  14.10.2009,  14:39 Найти цитируемый пост)
Э-э. Вроде они с любыми драйверами видеокарт, даже чипсетками всегда идут?

  Ну NVidia и Ati ничего драйвера, а про остальные такого не скажешь. 

Автор: ~FoX~ 14.10.2009, 16:31
Beltar, патрисающе  smile у меня 125 и 750 соответственно...
Тогда могу порекомендовать, рисовать прямо в памяти и по окончании выводить уже готовый битма на какой то контент, это реально быстрее будет работать..

Автор: Hexel 22.10.2009, 14:22
дурацкий вариант, но все же предложу)

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

или чисто графические приемы растушевки (как в фотошопе) - расширения границы выделения. только не спрашивай, как это сделать просто. поищи в исходниках фотошопа   smile 

Автор: AntonN 22.10.2009, 16:53
рисуйте не на canvas, а на буферном Tbitmap.canvas, который по окончании выводите на тот самый canvas

Автор: Beltar 22.10.2009, 17:05
Попиксельный доступ, это вообще ужас будет. Я на выходных повспоминал, чего там с DirectX делать, вспомнил про буфер индексов, мне стало грустно от всех этих сложностей, с OpenGL вроде легче получается. Пока сей вопрос решил отложить, установив линии единичной толщины по умолчанию, что вроде как всех устраивает и реализовывать остальной функционал проекта, мне там до конца годы работы должно хватить. Потом скорее всего, попробую на OpenGL отрисовку повесить.

Цитата

Тогда могу порекомендовать, рисовать прямо в памяти и по окончании выводить уже готовый битма на какой то контент, это реально быстрее будет работать..


А не то же самое обеспечивает свойство DoubleBuffered? Впрочем еще поэксперементирую.

Автор: Hexel 22.10.2009, 17:22
Beltar, ты, наверное, не понял сути первого предложения, попробую объяснить проще.

строишь на прозрачном канвасе (в буфер) график единичной толщины. потом процедурой типа BitBlt копируешь его ВЕСЬ, а не попиксельно, например, по вертикали со сдвигом на 1 пиксель (вверх или вниз). в итоге у тебя получится график толщиной в 2 пикселя. сколько раз выполнишь этот шаг, такой толщины у тебя и будет линия.

копировать можно и по горизонтали, и по диагонали. это смотря, какой вид графика? разрывный? гладкий, без завитушек? какая система координат, прямоугольная или полярная?

на крайняк, скинь мне исходник, если хочешь. ту часть, которая отвечает за вывод графики, может помогу.

Автор: AntonN 23.10.2009, 13:34
Hexel, тормозить будет больше, чем рисовать одинарную линию 2 раза со сдвигом в нужную сторону (по перпендикуляру к линии) средствами canvas.lineto()

Автор: Hexel 23.10.2009, 13:39
Beltar, можно размеры канваса, количество точек, вид графика, тип системы координат? 

сам попробую, а то что-то не верится, что построить 50000 точек lineto быстрее, чем скопировать изображение растровыми методами

Автор: AntonN 23.10.2009, 14:05
Hexel,  на картинке 100х100 будет 10к точек которые ты будешь перебирать при построении каждой линии? к тому же "скопировать" понятие размытое, что будет если нужно будет построить 200 цветных линий под разными углами друг у другу, алгоритм твой не сдуется? smile

Автор: Hexel 23.10.2009, 20:14
хорошо. не буду голословным. раз уж меня не понимают со слова, выражусь более кратко

(конкретную буфферизацию я не умею делать, но думаю, вы поймете идею)

Автор: Beltar 23.10.2009, 20:58
Прежде чем скопировать изображение с битмепа на битмеп, его надо еше сначала на буферном битмепе нарисовать. Код завтра дооформлю и если хочешь могу выложить. График предназначен для отрисовки архивных параметров технологических процессов, потому система координат декартова, но с нулем в углу, а вероятность попадания соседних замеров в одну экранную точку очень высокая, к сожалению при росте разршения она резко уменьшается.

Автор: Hexel 24.10.2009, 00:00
еще раз повторюсь: я не умею рисовать на буферном битмапе, поэтому для наглядности метода просто взял 2 паинт бокса

ну хоть теперь я ясно выразил свою идею?

ЗЫ: если не сложно, выклади свой замер производительности. а то мне сложно судить, у меня атлон х2 4200 (2.2ГГц) х ДДР2 800МГц smile его натупило только на 5 лимонов точек (около 3 сек)

и как вообще эта произодительность замеряется? gettickcount?

Автор: Beltar 24.10.2009, 18:00
Попробовал я порисовать на буферном битмепе, результат поразителен отсутствием какой-либо разницы от рисования сразу на PaintBox'е, при отключении отрисовки, ни синусы, ни создание и удаление большого битмепа не смогли занять одно ядро E-4500 (2.2 HHz) на время  большее времени выделяемому процессу.

2 Hexel:

Сырец контрола можешь посмотреть. D 2007 Эксперементировал я с твоим примером, получилось вот что.

Автор: Beltar 24.10.2009, 18:04
Второй аттач. Да, в контроле там много чего inline помечено, без них по идее должно скомпилиться в семерке, а может и нет.

Автор: Hexel 25.10.2009, 20:58
да, щас скачаю, погляжу. кстати, нашел, как рисовать в буферном битмапе, но разницы не заметил)))

вот ято я хотел сказать после некоторых размышлений. для технической задачи:

1) лучше применить векторную графику. лучше выглядит, проще обрабатывается (например, масштабируется, анализируется), и, главное, легче всего подготавливается в печать.
2) не знаю, что у тебя за график, но иногда его выгодно апроксимировать какой-то функцией, нас так в универе учили. если надо - помогу, у меня с математикой проблем нет. такая фича позволяет устранить посторонние шумы, например несистемные ошибки измерений (график выглядит более гладким). такой график, кстати, можно рисовать кривой Безье, буквально за несколько вызовов.

по второму с выводом не спеши, посоветуйся со спецами

Добавлено через 10 минут и 16 секунд
Delphi6 нискампилил
инлайны поубивал (я, кстати, и не знаю, что это), но обламался на
Код

    //разные внутренние переменные
    FS:TFormatSettings;

([Error] AvgChart.pas(180): Undeclared identifier: 'TFormatSettings')

меня вот мучит вопрос: а что такого прикольного в новых IDE, помимо компонент, которые можно скачать с инета? до сих пор моей 6 мне хватало с головой, еще и оставалось

Автор: Hexel 25.10.2009, 21:20
насчет моего метода - да, разница в скорости есть. 500 тиков (мс?) для рисования толщиной 5, и 46 тиков  для рисвания той же толщиной (или толщиной 50) с помощью CopyRect. в буфер, потом на паинтбокс

Добавлено через 3 минуты и 6 секунд
при том, как оказалось, ВСЕ это время уходит на LineTo ))))

Автор: Beltar 8.2.2010, 13:26
Так, я-таки решил сделать отрисовку диаграммы на буферном битмепе.

Код

procedure TAvgChart.Draw;
begin
if BlockPaint then Exit;
DrawTrends;//Рисует на битмепе
FastRepaint;//Дорисовка


Код

procedure TAvgChart.FastRepaint;
begin
if BlockPaint then Exit;
Canvas.Pen.Mode:=pmCopy;
Canvas.Draw(0,0,Bmp);
if FSetsRectangles then DrawRectangles;
DrawAxes;
DrawValueLines;
if FInSelection then DrawSelection;
end;


В интересующих случаях (из MouseMove, MouseUp и Click контрола) FastRepaint вызывается напрямую, в отличие от пред. версии ничего не лагает, но зато неприлично мерцает. Все, что после Canvas.Draw(0,0,Bmp); никакого влияния на мерцание не оказывает. Как побороть?

Автор: AntonN 8.2.2010, 14:36
вместо Canvas.Draw() юзать сразу BitBlt().

что такое вот это?
Код

if FSetsRectangles then DrawRectangles;
DrawAxes;
DrawValueLines;
if FInSelection then DrawSelection;


вся идея в том, чтобы вообще все рисовать на буфере. Если рисование подложки графика ресурсовемко и нечастое, можно использовать два буфера, на первом рисовать подложку, на второй его копировать и наносить маркеры. Если мало - три буфера smile
В итоге вывод на форму будет краток - один вызов BitBlt()

Автор: Beltar 8.2.2010, 16:03
Обошелся двумя буферами, итоговый код FastRepaint:

Код

procedure TAvgChart.FastRepaint;
begin
if BlockPaint then Exit;
Canvas.Pen.Mode:=pmCopy;
if Assigned(Bmp1) then Bmp1.Free;
Bmp1:=TBitmap.Create;
Bmp1.Width:=Width;
Bmp1.Height:=Height;
Bmp1.Canvas.Draw(0,0,Bmp);
if FSetsRectangles then DrawRectangles;
DrawAxes;
DrawValueLines;
if FInSelection then DrawSelection;
Canvas.Draw(0,0,Bmp1);
end;


В кишках всех этих DrawSomething просто перед каждый канвасом приписал Bmp1. smile Хотя почему так получается мне непонятно, изначально ведь все рисовалось по целевому канвасу и ничего не мерцало, точнее хватало DoubleBuffered чтобы подавить мерцание.

Автор: AntonN 8.2.2010, 16:11
ты создаешь каждый раз битмап, но не удаляешь его.

а буферные битмапы и есть буферизация smile

Автор: Beltar 8.2.2010, 16:44
Цитата

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


if Assigned(Bmp1) then Bmp1.Free;
Если бы тут текло, то у меня, наверное, оперативки хватило бы на несколько минут максимум т. к. терять при каждом движении мышки с зажатой кнопкой экземпляр битмепа размером в пару метров должно быть приличным экстримом. Х)

Хотя при разрушении компонента действительно забыл.

Автор: AntonN 9.2.2010, 00:15
Я не посмотрел, думал он локально используется. Тогда и смысла нет его каждый раз пересоздавать, создай при создании формы один раз, да и усе

Автор: Beltar 9.2.2010, 02:17
Там лажа с неочисткой битмепа получалась, поэтому не мудрствуя лукаво сделал так.

Автор: Mikel 9.2.2010, 12:45
Для начала попробуй все рисовать в отдельном bitmap буфере, потом bitblt на paintbox. И кстати, зачем рисовать несколько десятков тысяч точек на дисплее с разрешением по Х порядка тысячи точек? Если график на столько подробный, можно рисовать прямо в памяти битмапа, без обращения к APIшному SetPixel. А вообще подумайте стоит ли их все рисовать smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)