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

Модуль DirectXGraphics



Итак, приступаем к изучению части DirectX, связанной с трехмерной графикой, хотя наши первые построения будут выполняться на плоскости. Теперь большая часть изученного в предыдущих главах книги непосредственно нами использоваться не будет, но многие подходы, понятия и приемы, с которыми мы уже познакомились, перекликаются с новым материалом. Например, в наших проектах мы встретим знакомые понятия главного объекта и порождаемых его методами вспомогательных объектов, и последовательность выполняемых нами действий будет во многом родственна предыдущим примерам.
Начнем наш путь с простейшего примера, проекта каталога Ex01. Если DirectDraw было удобнее начинать изучать с полноэкранных приложений, то с Direct3D мы познакомимся на примерах оконных приложений. В первом проекте данной главы клиентская часть окна окрашивается синим цветом. Это минимальное приложение, использующее Direct3D. Окно непрерывно перерисовывается, а в его заголовке выводится значение FPS.
Вначале бегло посмотрим код, потом некоторые ключевые моменты обсудим подробнее.
Прежде всего, замечаем, что в списке uses модуля дописан модуль DirectXGraphics. Это базовый модуль, играющий для наших последующих примеров такую же роль, какую играл ранее модуль DirectDraw. В этом модуле содержится описание базовых интерфейсов, типов и констант.
Имя формы этого и последующих примеров я задал frmD3D.
В разделе private описания класса формы мною внесены следующие строки:

FD3D IDIRECT3D8; // Главный объект
FD3DDevice IDIRECT3DDEVICE8; // Объект устройства
FActive BOOL; // Вспомогательный флаг
ThisTickCount DWORD; // Отсчет времени для подсчета FPS
LastTickCount DWORD;
function InitDSD : HRESULT; // Инициализация системы
function Render HRESULT; // Воспроизведение
procedure Cleanup; // Удаление объектов
procedure ErrorOut (const Caption : PChar; const hError : HRESULT);

Сообщение об ошибке выводится пока в отдельном окне, для оконных приложений здесь не должно возникать проблем:

procedure TfrmDSD.ErrorOut (const Caption : PChar;
const hError : HRESULT);
begin
FActive := False; // Остановить перерисовку окна
Cleanup; // Удалить все объекты
MessageBox (Handle, PChar(DXGErrorString (hError)), Caption, 0)
end;


Функция DXGErrorString возвращает описание ошибки, код которой передается в качестве аргумента. Эта функция представлена в модуле Directxcraphics.
В процедуре очистки памяти объекты высвобождаются знакомым нам способом:

procedure TfrmD3D.Cleanup;
begin
if Assigned (FDSDDevice) then begin
FD3DDevice._Release;
FD3DDevice := nil;
end;
if Assigned (FD3D) then begin
FD3D._Release;
FD3D := nil;
end;
end;

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

Function TfrmD3D.InitD3D : HRESULT;
var
d3ddm : TD3DDISPLAYMODE; // Вспомогательные структуры
d3dpp : TD3DPRESENT_PARAMETERS;
hRet : HRESULT;
begin
FD3D := nil;
FD3DDevice := nil;
// Создаем главный объект
FD3D := Direct3DCreate8(D3D_SDK_VERSION);
if FD3D = nil then begin
Result := _FAIL;
Exit;
end;
// Получаем установки рабочего стола
hRet := FDSD.GetAdapterDisplayMode(D3DADAPTERJ3EFAULT, d3ddm);
if FAILED(hRet) then begin
Result := hRet;
Exit;
end;
// Заполняем структуру, задающую параметры работы
ZeroMemory(@d3dpp, SizeOf(d3dpp)); // Обнуляем поля
with d3dpp do begin
Windowed := True; // Используется оконный режим
SwapEffect := D3DSWAPEFFECT_DISCARD; // Режим переключения буферов
BackBufferFormat := d3ddm.Format; // Формат заднего буфера
end;
// Создаем вспомогательный объект, объект устройства
Result := FD3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Handle,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
d3dpp, FD3DDevice);
end;

Главным интерфейсом является СОМ-объект класса IDIRECTSDS, методы которого позволяют получить доступ к функциям библиотеки. Главный объект создается первым, а уничтожается последним. Создается он с помощью функции Direct3DCreate8, единственным аргументом которой является константа, сообщающая системе, какая версия DirectX SDK использовалась при компиляции приложения.
Методы главного объекта позволяют узнать текущие установки видеосистемы, и следующим действием нашей программы служит вызов метода GetAdapterDisplayMode. Как правило, обязательным это действие является только для оконных приложений, поскольку такие установки требуются для задания параметров заднего буфера.
У метода GetAdapterDispiayMode два аргумента:



  • константа, задающая адаптер, для которого запрашиваются установки;
  • указатель на вспомогательную переменную, в которую помещается результат, являющийся описанием характеристик устройства.
Предопределенным значением первого аргумента пока может использоваться только D3DADAPTER_DEFAULT, нулевая константа, соответствующая первичному устройству. Для описания характеристик служит переменная типа TD3DDISPLAYMODE, запись:



TD3DDisplayMode = packed record
Width : Cardinal; // Ширина рабочего стола
Height : Cardinal; // Высота рабочего стола
RefreshRate : Cardinal; // Частота регенерации
Format : TD3DFormat; // Формат пиксела
end;

То есть, чтобы вывести текущую ширину рабочего стола, можно вставить такую строку:

ShowMessage (IntToStr (d3ddm.Width));

Значением частоты регенерации для основного устройства мы получим ноль.
Последний элемент записи позволяет узнать формат пиксела. Возможные значения этого поля перечислены в модуле DirectxGraphics. Все они начинаются на префикс "DЗDFМТ_". Констант довольно-таки много, я не стану детально рассматривать их все, только посмотрим, как можно идентифицировать две наиболее распространенных:

case d3ddm.Format of
D3DFMT_X8R8G8B8 : ShowMessage ('Формат пиксела: 32-битный RGB.');
D3DFMT_R5G6B5 : ShowMessage ('Формат пиксела: 16-битный 5-6-5.');
else ShowMessage ('Формат пиксела в списке отсутствует. ') ;
end;

Примечание
Обратите внимание, что при цветовой палитре рабочего стола, меньшей 16 бит на пиксел, работа DirectSD невозможна.

На следующем шаге инициализации задаются параметры работы, заполняются поля структуры типа TDSDPRESENT^PARAMETERS. В этом примере я выполняю только минимальный набор обязательных действий.
Логическое значение поля windowed задает режим работы приложения: наше приложение должно работать в оконном режиме. В поле swapEffect заносится константа, задающая порядок работы с задним буфером. Я использую константу D3DSWAFEFFECT_DiscARD, соответствующую режиму, при котором DirectX не заботится о сохранности содержимого заднего буфера при циклическом переключении страниц. В поле BackBufferFormat помещается формат пиксела для заднего буфера. Именно здесь необходимы полученные на предыдущем шаге характеристики рабочего стола.
И после этого вызывается метод главного объекта createDevice, с помощью которого создается дочерний интерфейс типа IDIRECTSDDEVICES. Объект такого типа представляет собой непосредственно устройство вывода. Собственно, с помощью его методов и производятся воспроизведение и модификация изображения. У метода CreateDevice шесть аргументов. Первым является устройство вывода, используемая здесь константа нам уже знакома. Вторым аргументом передается константа, задающая тип воспроизведения: использовать или нет аппаратное ускорение. Указание в качестве аргумента константы D3DDEVTYPE_HAL соотвстствют первому случаю, второму - D3DDEVTYPE_REF. Еще одна возможная константа - D3DDEVTYPE_sw, предназначена для подключения встраиваемых модулей, зарегистрированных в DirectX.



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

Третьим аргументом метода createDevice задается идентификатор окна, в котором осуществляется вывод, свойство Handle формы хранит значение этого идентификатора. Следующий параметр задает порядок работы с вершинами: обрабатываются математические операции центральным процессором либо ускорителем. Здесь мы будем использовать константу DSDCREATE SOFTWAREJ/ERTEXPROCESSING, чтобы наши профаммы работали на всех графических картах. Пятый, предпоследний, аргумент метода createDevice - переменная типа TDSDPRESENT^PARAMETERS, с помощью которой мы передаем заполненную нами ранее структуру. В ней же будут содержаться скорректированные системой значения устанавливаемого режима. Например, количество задних буферов в примере задается первоначально равным нулю, система скорректирует это значение при создании объекта устройства. Добавьте в код следующую строку:

ShowMessage (IntToStr(d3dpp.BackBufferCount));

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

procedure TfrmDBD.FormCreate(Sender: TObject);
var
hRet : HRESULT;
begin
hRet := InitD3D;
if Failed (hRet} then ErrorOut ('InitD3D', hRet);
end;

Основная нагрузка в примере ложится на функцию Render, в которой выполняется единственное действие - экран окрашивается синим цветом:

function TfrmDSD.Render : HRESULT;
var
hRet : HRESULT;
begin
// Инициализация не выполнена, либо произошла серьезная авария
if FDSDDevice = nil then begin
Result := E__FAIL;
Exit;
end;
hRet := FD3DDevice.Clear(0, nil, D3DCLEARJTARGET,
D3DCOLOR_XRGB(0, 0, 255), 0.0, 0); // Очистка заднего буфера
if FAILED(hRet) then begin
Result := hRet;
Exit;
end;
// Переключение буферов устройства
Result := FD3DDevice.Present(nil, nil, 0, nil);
end;



Начинается код функции с проверки присутствия объекта устройства. Этот объект может отсутствовать, если инициализация не выполнилась успешно, либо объект потерян. Последняя ситуация может возникнуть, когда, например, по ходу работы приложения меняются установки рабочего стола. Обратите внимание, что при отсутствии объекта устройства наша функция Render возвращает значение E_FAIL, но функция обработки ошибки DXGErrorString в ответ на такую ошибку возвращает строку 'Unrecognized Error' (Неопознанная ошибка). Вы можете избавиться от неопределенности сообщения, введя собственную константу на случай потери объекта устройства.
Далее в функции Render вызывается метод clear объекта устройства. Первый и второй аргументы метода позволяют задавать очищаемую область: первый аргумент задает количество используемых прямоугольников, вторым аргументом передается указатель на массив, содержащий набор величин типа TRect.
Третьим аргументом метода clear уточняются параметры очистки. Здесь указывается флаг или комбинация флагов. Константа DSDCLEARJTARGET используется в ситуации, когда очищается цветовая поверхность устройства. Сам цвет, в который "перекрашивается" устройство, передается следующим параметром. В примере цвет, которым будет окрашено окно, идентифицируем, используя готовую функцию D3DCOLOR_XRGB. Ее аргументом является тройка весов чистых цветов, образующих нужный нам оттенок. Последние два аргумента метода пока оставим без рассмотрения, связаны они со специальными буферами.
Окрасив задний буфер чистым синим цветом, нам остается только переставить буферы - вызываем метод Present объекта устройства. Если третий аргумент метода нулевой, то идентификатором окна, в котором происходит работа, берется значение, установленное ранее, во время инициализации работы системы. Все остальные параметры метода или не используются, или при указанных нулевых значениях задают работу со всей клиентской областью окна, что, как правило, и необходимо.
В состоянии ожидания сообщений беспрерывно вызывается функция Render, если окно приложения не минимизировано:



procedure TfrmDBD.ApplicationEventslMinimize(Sender: TObject);
begin
FActive := False; // При минимизации окна приложения флаг опускаем
end;
procedure TfrmDSD.ApplicationEventslRestore(Sender: TObject);
begin
FActive := True; // Окно восстановлено, флаг поднимаем
end;

Помимо непрерывной перерисовки окна периодически подсчитывается и выводится в его заголовке значение FPS:

procedure TfrmDSD.ApplicationEventslIdle(Sender: TObject;
var Done: Boolean);
var
hRet : HRESULT;
begin
if FActive then begin // Только при активном окне Inc (Frames);
hRet := Render; // Перерисовка окна
if FAILED(hRet) then begin
FActive := False; ErrorOut ('Render', hRet);
Exit;
end;
ThisTickCount := GetTickCount;
if ThisTickCount - LastTickCount > 50 then begin
// Подсчет и вывод FPS
Caption := 'FPS = ' + Format('%6.2f',
[frames * 1000 / (ThisTickCount - LastTickCount)]);
Frames := 0;
LastTickCount := GetTickCount;
end;
end;
Done := False;
end;

Минимальное по сложности приложение, использующее DirectSD, мы разобрали, теперь попробуем проверить один момент. В проекте каталога Ех02 левая и правая половины окна окрашиваются в синий и красный цвета соответственно. Клиентская область окна имеет размер 300x300 пикселов. В функции Render для задания областей окрашивания используется переменная wrkRect типа TRect:

SetRect (wrkRect, 0, 0, 150, 300); // Левая область окна
hRet := FDSDDevice.Clear(1, @wrkRect, D3DCLEAR_TARGET,
D3DCOLOR__XRGB(0, 0, 255), 0.0, 0); // Первую область
// окрашиваем синим
if FAILED(hRet) then begin
Result := hRet;
Exit;
end;
SetRect (wrkRect, 150, 0, 300, 300); // Правая область
hRet := FDSDDevice.Clear(1, @wrkRect, D3DCLEAR_TARGET,
D3DCOLOR_XRGB(255, 0, 0), 0.0, 0); // Вторую область
// окрашиваем красным
if FAILED(hRet) then begin
Result :=0 hRet;
Exit;
end;

Проект каталога Ех03 вы сможете разобрать и без моей помощи, это несложная модификация предыдущего примера, отличающаяся только тем, что значения цветовых весов со временем меняются. Я же обращу ваше внимание на одну немаловажную особенность последних двух примеров: при изменении размеров окна его половинки окрашиваются так, будто в коде отслеживаются его текущие размеры. Делаем важный вывод: размеры клиентской области окна на момент инициализации Direct3D определяют область вывода на весь сеанс работы с графической системой.




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