DirectX Графика в проектах Delphi

       

Буферы



Итак, будем стараться использовать метод BitFast всегда, когда это возможно, т. к. скорость работы приложения является для графики наиважнейшей характеристикой. Посмотрим, как выглядит с применением этого метода перерисовка в проекте каталога Ех14. С помощью клавиш перемещения курсора можно управлять положением картинки на экране по горизонтальной оси. Хотя скорость воспроизведения и увеличилась, но мельтешение полос при перерисовке картинки осталось. Экран мы заполняем в два этапа: вначале все закрашиваем черным цветом, затем накладываем картинку. В промежуток времени между этими действиями экран успевает обновиться, и мы видим эти раздражающие полосы.
Попробуем проделать следующее: создадим внеэкранную вспомогательную поверхность, по размерам равную первичной. Воспроизводить изображение будем на нее, а окончательную картинку перенесем на экранную поверхность. Иллюстрацией этого ловкого приема служит проект каталога Ех15.
Поскольку значения устанавливаемых параметров экрана в коде используются неоднократно, введем соответствующие константы:

ScreenWidth = 640;
ScreenHeight = 480; ScreenBitDepth = 16;

Для загрузки растра вызовем вспомогательную функцию DDLoadBitmap из модуля DDUtii, объединяющую создание поверхности, собственно загрузку и копирование растра на поверхность:

FDDSImage := DDLoadBitmap (FDD, szBitmap, 0, 0); // Укороченный код
if FDDSImage = nil then begin // Произошла ошибка
ErrorOut (hRet, ' DDLoadBitmap' ) ;
Exit
end;

Функция пытается загрузить растр из ресурсов, в случае неудачи загружает файл. Первым аргументом задается главный объект DirectDraw, затем имя файла - у нас это константа szBitmap, - дальше указываются требуемые размеры поверхности или нули, если поверхность должна иметь размеры растра.
Вспомогательная поверхность носит имя FDDSBack и в начале и конце работы приложения "обнуляется" самой первой. Ее размеры задаются равными размерам первичной поверхности:

FillChar (ddsd, SizeOf (ddsd) , 0) ; with ddsd do begin
dwSize := SizeOf (ddsd) ;
dwFlags := DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH;
ddsCaps . dwCaps := DDSCAPS_OFFSCREENPLAIN;
dwWidth := ScreenWidth;
dwHeight := ScreenHeight; end;
hRet := FDD. CreateSur face (ddsd, FDDSBack, nil); if hRet <> DD_OK then begin
ErrorOut (hRet, 'Create Back Surface');
Exit
end;


Перерисовка окна принципиально изменилась тем, что весь вывод осуществляется на вспомогательную поверхность, содержимое которой в законченном виде помещается на первичную:

ZeroMemory(@ddbltfx, SizeOf (ddbltfx) ) ; ddbltfx.dwSize := SizeOf (ddbltfx) ; ddbltfx. dwFillColor := 0;
while True do begin // Закрашиваем фон вторичной поверхности черным hRet := FDDSBack. Bit (nil, nil, nil, DDBLT COLORFILL or DDBLT WAIT, @ddbltfx) ;
// Внеэкранная поверхность также может быть потеряна
if hRet = DDERR^SURFACELOST then begin if Failed (RestoreAll) then Exit;
end
else Break; end;
// Помещаем растр на вспомогательную поверхность while True do begin
hRet := FDDSBack.BltFast (1ft, 112, FDDSImage, nil, DDBLTFAST_WAIT);
if hRet = DDERR_SURFACELOST then begin if Failed (RestoreAll) then Exit;
end
else Break; end;
// Вспомогательная поверхность заполнена, блиттинг производится //на первичную while True do begin
hRet := FDDSPrimary.BltFast (0, 0, FDDSBack, nil, DDBLTFAST_WAIT);
if hRet = DDERR_SURFACELOST then begin if Failed (RestoreAll) then Exit;
end
else Break;
end;

Функция восстановления поверхности использует вспомогательную функцию перезагрузки растра DDReLoadBitmap модуля DDUtil:

function TfrmDD.RestoreAll : HRESULT; begin
Result = DD_FALSE;
if Succeeded (FDDSPrimary._Restore) then begin if Failed (FDDSImage._Restore) then Exit;
// Рекомендуется перезагрузить растр после восстановления if Failed (DDReLoadBitmap(FDDSImage, szBitmap)) then Exit;
// Добавилось восстановление еще одной вспомогательной поверхности
if Failed (FDDSBack.^Restore) then Exit;
Result := DD_OK;
end;
end;

Протестируйте приложение: никаких полос не возникает, все выглядит прекрасно. DirectDraw предлагает автоматизированный механизм двойной буферизации, аналогичный проделанному нами вручную. Посмотрим на примере проекта каталога Ех1б, как это делается. При создании первичной поверхности указываем количество задних буферов. Вместо одной константы у нас появилась комбинация нескольких флагов. Создаваемая поверхность является комплексной, состоящей из двух поверхностей - первичной и присоединенной к ней вторичной поверхности заднего буфера. Чтобы оговорить то, что "перебрасывание" (flipping) содержимого заднего буфера на первичную поверхность будет осуществляться DirectDraw без нашего участия, необходимо добавить флаг DDSCAPS_FLIP:





FillChar (ddsd, SizeOf(ddsd), 0);
ddsd.dwSize := SizeOf(ddsd);
// Сообщаем, что надо учесть наши пожелания о буфер заднего плана
ddsd.dwFlags := DDSD_CAPS or DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or
DDSCAPS_COMPLEX; // + комплексная поверхность + разрешить перебрасывание
ddsd.dwBackBufferCount := 1; // У поверхности есть один задний буфер
hRet := FDD.CreateSurface(ddsd, FDDSPrimary, nil);
if hRet <> DD_OK then begin
ErrorOut(hRet, 'Create Primary Surface');
Exit;
end;

Поверхность заднего буфера нам создавать не нужно, она появится без нашего участия, но для осуществления вывода на нее необходимо связать нашу переменную FDDSBack, для чего предназначен метод поверхности GetAttachedSurface. Первый аргумент метода - запись типа TDDSCaps2. С таким типом мы встречались, он является частью структуры TDDSurfaceDesc2. Здесь же указываем, что нам требуется адрес поверхности заднего буфера:

FillChar(ddscaps, SizeOf(ddscaps), 0); // Обнуляем все поля записи
// Оговариваем, что требуется адрес поверхности заднего буфера
ddscaps.dwCaps := DDSCAPS_BACKBUFFER;
// Получаем адрес присоединенной поверхности
hRet := FDDSPrimary.GetAttachedSurface(ddscaps, FDDSBack);
if hRet <> DD_OK then begin
ErrorOut(hRet, 'GetAttachedSurface');
Exit;
end;

Код воспроизведения изменился только в финальной части, вместо использования метода BitFast первичной поверхности вызываем ее метод Flip:

while True do begin
hRet := FDDSPrimary.Flip(nil, DDFLIP_WAIT) ;_
if hRet = DDERR_SURFACELOST then begin if Failed (RestoreAll) then Exit;
end
else Break;
end;

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

Примечание
Присоединенная поверхность не требует отдельного восстановления, она будет восстановлена как часть первичной, комплексной поверхности.

Обращаю внимание, что в программах, написанных на Delphi, необходимо обязательно при завершении работы освобождать присоединяемые поверхности, иначе возникнет исключение.
При использовании метода поверхности Flip не происходит, на самом деле, простого воспроизведения на ней так, как вытекает из моих предыдущих рассуждений. Буферы меняются местами, вернее, происходит переключение указателей (адресов). Сами объекты при этом местами не меняются.
Переходим к очередному примеру - проекту каталога Ех17. Смысл примера состоит в следующем: поместим на переднюю и заднюю поверхности разные образы и с течением времени будем только переключать их, не перерисовывая.
На переднюю поверхность я помещаю растянутую картинку с пейзажем, на задней поверхности (она как раз и является задним буфером) нарисован тот же пейзаж посреди черного поля. Код переключения буферов из обработчика OnPaint формы переместился в обработчик единственного события таймера, а начинается код, связанный с перерисовкой окна, с заполнения переднего буфера:



hRet := FDDSPrimary.Bit (nil, FDDSImage, nil, 0, nil) ;

Эта строка, а также код, связанный с заполнением заднего буфера, может спокойно перекочевать в обработчик OnCreate формы, он в этом примере вызывается и при восстановлении окна.
Чтобы таймер не работал при неактивном состоянии приложения, применяется событие объекта ApplicationEventsi, связанное с "уходом" окна приложения:

procedure TfrmDD.ApplicationEventslDeactivate(Sender: TObject);
begin
Timer1.Enabled := False; // Выключаем таймер Application.Minimize; // Минимизируем приложение
end;

Включается же таймер в обработчике OnCreate, специально для обработки восстановления окна.
Заполнение черным или любым другим цветом поверхности заднего плана для подобных примеров является обязательным. Если этого не делать, то в незаполненных участках буфера будет выводиться "мусор", искаженные следы работы системы с канвой рабочего стола. Если вы удалите этот код, вам может показаться, что все работает прекрасно, но только потому, что перед этим вы запускали приложение, аккуратно заполнившее экран.
Итак, интересен для нас этот несложный пример демонстрацией того, что при переключении буферов их содержимое не теряется, а перекочевывает в следующий буфер. Буферы заполняются при создании и восстановлении окна. В обработчике таймера происходит только переключение буферов.
В рассмотренном примере существуют два буфера. Это обычно для приложений, использующих DirectDraw. Если же буферов больше, то содержимым они меняются по цепочке (рис. 2.4).



Рис. 2.4. Переключение буферов происходит по цепочке

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


Содержание раздела