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

Лишние прорисовки происходят даже при рисовании на форме. Если выводить каждую линию, то прорисовка будет долгой.. Отследим процесс рисования на реальном примере, в котором будем рисовать на форме 10 ООО линий. Для этого создайте новый проект в Delphi и поместите на форму только одну кнопку. По ее нажатии (событие OnCl ick для кнопки) пишем код из листинга 2.3.

Листинг 2.3. Рисование линий на форме

procedure TForml.ButtonlClickCSender: TObject); va г

i: Integer: tDrawTime: Cardinal : begin
tDrawTime = GetTickCountO;
Repaint:
for i := 0 to 10000 do begin
Canvas MoveTo(randomCWidth), Random(Height)): Canvas.Li neTo(random(Width). Random(Height)):
end;
ShowMessage(IntToStr(GetTickCount()-tDrawTime)):
end;

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

В самом начале вызывается метод формы Repai nt, который заставляет ее обновить содержимое. В данном случае обновление очищает форму, потому что в этот момент генерируется событие OnPai nt, по которому мы ничего не рисуем.

После этого запускается цикл из 10 000 шагов, в котором рисуются линии со случайными координатами. Если запустить приложение и нажать на кнопку, то даже на быстром компьютере будет видно, как линии засыпают экран. На моем компьютере (Celeron 2,4 ГГц) засыпание происходило быстро, но все же заметно.

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

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

Теперь поместите на форму еще одну кнопку, и по ее нажатии напишем код рисования с буферизацией (листинг 2.4). Здесь мы сначала создадим объект массива битов типа TBitmap, который позволяет хранить данные изображения и рисовать в нем знакомыми нам функциями работы с графикой После этого установим ширину и высоту равными размеру окна. Далее используем заливку содержимого изображения цветом clBtnFace (этот цвет соответствует системному цвету заливки кнопок и фона диалоговых окон).

Теперь можно переходить непосредственно к рисованию линий. Код ничем не отличается от уже рассмотренного выше (см. листинг 2.3), только рисуем мы не на форме, а в объекте TBitmap. Рисование в этом объекте происходит даже медленнее, чем на экране. Это уже особенность, от которой никуда не деться, зато мы получим возможность пользоваться теми же функциями, что и для рисования на экране.

Листинг 2.4. Рисование с буферизацией

procedure TForml.Button2Click(Sender: TObject); var
i: Integer: Mmage: TBitmap;
tDrawTime. Cardinal: begin
tDrawTime •= GetTickCountO;
blmage := TBitmap.Create.
blmage.Width := Width;
blmage.Height .= Height.
blmage Canvas.Brush.Col or := clBtnFace: blmage.Canvas.Fi11Rect(blmage.Canvas.CIipRect);
for i := 0 to 10000 do begin
blmage.Canvas.MoveTo(random(Width), Random(Height));
blmage.Canvas.LineTo(random(Width). Random(Height)).
end:
Canvas.Draw(0. 0. blmage);
ShowMessagedntToStr(GetTickCountO-tDrawTime));
end;

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

Если сейчас запустить приложение и выполнить его, то вы сможете ощутить потерю производительности в несколько раз. Этосвязанос тем, что методы MoveToи Line-To в объекте TBitmap работают слишком медленно. Даже медленнее, чем у формы Canvas. Зато мы избавились от видимого эффекта построения.

Для нового примера я бы воспользовался чем-то более скоростным, чем метод Draw, но сейчас мы изучаем буферизацию и оставим пока этот метод.

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

Где слабое место? Самое слабое - это цикл for, который выполняется 10 ООО раз. Не менее слабым местом является процедура. Если она будет вызываться по таймеру несколько раз, то это равносильно тому, что процедура находится в цикле и выполняется с задержкой во времени. Давайте посмотрим на процедуру с точки зрения цикла.

Так как рисование будет происходить часто, то нет смысла каждый раз создавать изображение TBitmap и устанавливать его размеры. Данные операции можно вынести за пределы процедуры. Для этого объявим переменную bStartlmage типа TBitmap в разделе private объявления нашей формы. После этого нужно создать обработчик события OnCreate для формы и инициализировать изображение там:

procedure TForml.FormCreateCSender: TObject): begin
bStartlmage := TBitmap.Create: bStartlmage.Width :- Width;
bStartlmage.Height := Height;
end;

В обработчике события OnClick для кнопки можно рисовать только в созданном объекте TBitmap (листинг 2.5).

Листинг 2.5. Рисование линий

procedure TForml.Button3Click(Sender: TObject); va г

i: Integer: tDrawTime: Cardinal : begin
tDrawTime := GetTickCountO ;
bStartlmage.Canvas.Brush.Col or .= clBtnFace:
bSta rtImage.Canvas.Fi 11Rect(bSta rtImage.Canvas.Cli pRect):
for i := 0 to 10000 do begin
bStartlmage.Canvas.MoveTo(random(Width). Random(Height));
bStartlmage.Canvas.LineTo(random(Width). Random(Height));
end;
Canvas.Draw(0. 0. bStartlmage);

ShowMessage(IntToStr(GetTickCountО-tDrawTime)):

end:

На моем компьютере (Celeron 2400,512 Мбайт ОП) этот код работал 260 мс, а код из листинга 2.4 работал 282 мс.

Конечно же, графика с использованием средств GDI намного медленней, чем DirectX, и там можно было бы оптимизировать еще лучше. Или хотя бы отказаться от использования объекта TBitmap, а использовать блок памяти. В обоих случаях придется забыть методы MoveTo или LineTo, а применять собственный алгоритм рисования прямой линии. Это уже из серии графических алгоритмов и тема для отдельного разговора.

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

ПРИМЕЧАНИЕ -

Исходный код рассмотренного здесь примера находится на компакт-диске в каталоге Sources\ch02\DrawProject.

2.9. Лишние прорисовки экрана || Оглавление || 2.11. Многопоточность


Delphi в шутку и всерьез: что умеют хакеры



Новости за месяц

  • Январь
    2022
  • Пн
  • Вт
  • Ср
  • Чт
  • Пт
  • Сб
  • Вс