Михаил Краснов - Графика DirectX в Delphi
procedure TfrmDD.FormCreate(Sender: TObject); var
hRet : HRESULT; // Для анализа успешности действий
ddsd : TDDSurfaceDesc2; // Вспомогательная структура begin
FDDSPrimary := nil; //В начале работы обнуляем все СОМ-объекты
FDD := nil;
// Создание главного объекта DirectDraw
hRet := DirectDrawCreateEx (nil, FDD, IDirectDraw7, nil);
if hRet <> DD_OK then begin
ErrorOut(hRet, 'DirectDrawCreateEx'); Exit;
end;
// Задаем уровень кооперации
hRet := FDD.SetCooperativeLevel(Handle, DDSCL_FULLSCREEN or
DDSCL_EXCLUSIVE); if hRet <> DD_OK then begin
ErrorOut(hRet, 'SetCooperativeLevel'); Exit;
end;
// Заполняем поля вспомогательной структуры
FillChar(ddsd, SizeOf(ddsd), 0); // Для начала все поля обнуляем ddsd.dwSize := SizeOf(ddsd); // Поле размера структуры ddsd.dwFlags := DDSD_CAPS; // Будет создаваться первичная поверхность ddsd.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE;
// Собственно создание первичной поверхности
hRet := FDD.CreateSurface(ddsd, FDDSPrimary, nil); if hRet <> DD_OK then begin
ErrorOut(hRet, 'Create Primary Surface');
Exit;
end;
end;
Прежде чем мы разберем подробно все новое, обращаю внимание, что при завершении работы приложения привычным для нас способом освобождаем переменные в порядке, обратном их связыванию:
procedure TfrmDD.FormDestroy(Sender: TObject); begin
if Assigned(FDD) then begin // Связана ли переменная главного объекта
// Связана ли переменная первичной поверхности
if Assigned(FDDSPrimary) then FDDSPrimary := nil;
FDD := nil;
end;
end;
При запуске проекта чего-либо особенного не происходит, только окно оказывается распахнутым на весь экран, хотя в свойствах формы ничего подобного не указано. Обратите внимание также, что окно хоть и распахнуто, но не максимизировано, мы можем изменять его размеры привычным способом.
Возьмите за правило не запускать проекты, использующие DirectDraw, под управлением среды Delphi. Запускайте непосредственно откомпилированный модуль.
Сразу после создания главного объекта мы задаем уровень кооперации, используя метод setCooperativeLevei. Уровень кооперации определяет уровень взаимодействия приложения с экраном и с другими приложениями. И это действие - задание уровня кооперации - является обязательным для всех приложений, использующих DirectDraw. Метод имеет два параметра: первый является идентификатором окна приложения, здесь мы передаем значение свойства Handle формы, второй параметр представляет собой одно из строго определенных значений, либо комбинацию таких значений.
Используемая комбинация битовых флагов записана в этом примере как "DDSCL_FULLSCREEN or DDSCL_EXCLUSIVE". В подобных случаях всегда слово or можно заменить знаком +. Первый флаг задает полноэкранный режим работы приложения, второй - монопольный режим доступа к экрану. Оба флага в комбинации должны присутствовать.
В Delphi подобные комбинации битовых значений редко встречаются, поэтому для начинающих требуются пояснения. Такие комбинации используются для передачи одним аргументом нескольких параметров. Константы, указанные в комбинации, представляют собой степени двойки. Из суммы таких чисел легко выделить присутствие каждой константы: наличие единицы в разряде двоичного числа.
Итак, в рассматриваемом примере запрашивается полноэкранный режим работы приложения, и по правилам, установленным DirectX, необходимо для такого режима установить эксклюзивный уровень доступа. Теперь понятно, почему окно распахивается на весь экран.
Дальше по коду создается первичная поверхность. Поверхность является одним из основных понятий DirectDraw, за этим термином скрывается прямоугольный блок памяти. Поясню смысл этого понятия на примере анимации, которую можно организовать, скажем, следующим образом: кадры анимации хранятся в отдельных блоках памяти, и с течением времени на экран выводится нужный блок. Блок, хранящий отдельный кадр, и блок, соответствующий экрану, называются поверхностями. О поверхностях можете думать как о растрах, размещенных в памяти, или как об объектах класса TBitmap.
После изрядной практики в использовании компонентов Delphi и рисовании с помощью методов canvas, начинающие программисты в DirectDraw испытывают при знакомстве с термином поверхности вполне объяснимую неловкость. Некоторые вещи здесь могут показаться новичку неудобными и неясными. В дальнейшем, с практикой, придет полное понимание всех моментов смысла поверхности и работы с ней, а сейчас вы должны твердо для себя уяснить, что экран для будущей работы вы обязаны подготовить сами, и производится это в терминах поверхности. Даже если у вас не будет на экране картинок, а вы хотите просто порисовать так, как привыкли это делать с помощью методов Canvas.
Создаются поверхности с помощью метода CreateSurface главного объекта, но для задания свойств создаваемой поверхности используется вспомогательная величина типа TDDSurfaceDesc2. Представляет она собой структуру - запись в традиционной для Delphi терминологии. Значения ее полей содержат параметры создаваемой поверхности. Только в редких случаях необходимо определять значения абсолютно всех параметров. Как правило, можно обойтись только парой из них. Остальные поля DirectX заполнит за нас. В любом случае, следует обязательно обнулить все поля структуры. Это очень важное правило. В нашем примере поля обнуляются с помощью функции Filichar, но помните, что это же можно сделать посредством другой функции, ZeroMemory:
ZeroMemory (@ddsd, SizeOf(ddsd));
Следующее действие также является обязательным: в поле dwsize надо записать размер структуры. Оба действия, обнуление всех полей и установка размера, необходимо выполнять в начале работы с любой структурой, встречающейся нам в DirectX. Пренебречь каким-либо из них у нас просто не получится. Проверьте сейчас же: удалите любую из этих строк и запустите проект. Выполнение кода обработчика onCreate завершится аварийно. Авария, впрочем, для этого проекта не является фатальной. Исключение связано с DirectDraw и пока не приводит к полному провалу работы приложения. Запомните, как выглядит окно выдачи расшифровки ошибки - работа нашей пользовательской функции ErrorOut, чтобы сразу же отличать аварийные ситуации, связанные с DirectX.
Итак, неиспользуемые поля структуры будут нулевыми. Главное поле, которое нам надо обязательно заполнить, - это поле ddsCaps, представляющее описание возможностей (capabilities) - наиважнейших характеристик поверхности. Поле это также является структурой, из всех полей которой мы обязаны задать, как минимум, значение поля dwCaps.
Поле dwFiags структуры TDDSurfaceDesc2 содержит указания, какие из ее полей заполнены нами и должны быть приняты системой в расчет. Присвоив этому полю значение здесь DDSD_CAPS, мы указываем DirectDraw, что нами заполнено именно поле ddsCaps, а все остальные параметры создаваемой поверхности отдаются на откуп графической системе, и она будет распоряжаться ими по собственному усмотрению. Если мы поместим в поля структуры значения, но забудем указать это в поле флагов, графическая система установленные значения в расчет принимать не будет.
Вывод в DirectDraw осуществляется на поверхностях, которых может быть столько, сколько нам надо. Предназначение и параметры каждой из них мы задаем, исходя из логики приложения. Поверхности можно использовать и как хранилища вспомогательных ресурсов - битовых растров, чем мы и будем пользоваться достаточно часто.
Но среди всех поверхностей есть одна, особая, связанная непосредственно с экраном. Все, что на нее будет выведено, окажется отображенным прямо на экране монитора. Называется эта поверхность первичной. Она обязательно создается в любом проекте, рисующем что-либо на экране с помощью DirectDraw.
В поле dwCaps структуры ddsCaps, являющейся, в свою очередь, частью структуры ddsd, заносим значение DDSCAPS_PRIMARYSURFACE, вызывая метод CreateSurface главного объекта. У метода три аргумента: адрес структуры, описывающей параметры создаваемой структуры, переменная, в которую помещается результат - созданная поверхность. Третий параметр зарезервирован для будущих нужд, а пока обязан быть установлен в nil.
Далее мы традиционным образом оцениваем успешность проделанной операции, и в случае ее провала сообщаем об ошибке и выполняем выход из программы. Строго говоря, здесь мы завершаем работу приложения в любом случае, независимо от успеха создания поверхности - ведь код на этом заканчивается.
Точно так же, как и в примере первой главы, равенство nil переменной поверхности является признаком неудачи предыдущей операции.
Мы разобрали все действия в нашем примере, и все они должны быть вам понятны.
Давайте повторим порядок действий:
* создается главный объект; задается уровень взаимодействия приложения с системой и другими приложениями; заполняются поля структуры, хранящей параметры поверхности; создается, как минимум, одна поверхность - первичная, связанная с экраном.
Поначалу все это может показаться чересчур громоздким, но по мере накопления опыта у вас появится легкость понимания кода.
Начинающих программистов может смущать кажущаяся запутанность с цифрами в типах переменных, скажем, тип TDDSurfaceDesc2 заканчивается не на 7. Одни типы, например интерфейсы, менялись с каждой версией, другие же вспомогательные типы модифицировались реже, поэтому их цифры "отстают" от нумерации используемых интерфейсов.