Delphi - база знаний

         

DirectX (Игровой SDK)


DirectX (Игровой SDK)





Автор: Стас Бакулин

Взято из



DirectX (Игровой SDK) 1






Модель компонентных объектов (СОМ)

Перед углублением и изнурительные подробности DirectDraw сначала несколько слов о модели компонентных объектов - кратко СОМ. Delphi использует объектно-ориентированный язык программирования Object Pascal. Дизайнеры Delphi решили сделать родные Delphi объекты полностью совместимыми с СОМ и OLE. Это большая новость для нас, потому что DirectDraw использует интерфейс СОМ и поэтому из Delphi получить к нему доступ достаточно просто.

Объекты СОМ подробно освещены в разделе Delphi. Но для того, чтобы сэкономить ваше время, предоставлю краткий обзор. В Delphi вы работаете с объектом СОМ практически так же, как и с другим объектом. Объекты СОМ выглядят по сути как обычные объекты Delphi. Они имеют методы, которые вы вызываете для доступа к их услугам. Тем не менее, они не имеют полей или свойств. Главным отличием является то, что вы вызываете метод Release вместо метода Free, если вы хотите освободить эти объекты.

Вы также никогда не создаете объект СОМ путем вызова конструктора. Вместо этого вы вызываете функцию в DirectDraw для создания главного объекта DirectDraw. Этот объект имеет дальнейшие методы, которые вы используете для создания других методов. Помимо этих двух вопросов вы можете фактически думать о них как об объектах Delphi.

Объекты СОМ DirectDraw определяются в довольно сложном файле-заголовке на С, который поставляется с Game SDK. Однако я перевел это в модуль импорта, который вы можете использовать в Delphi. Это файл DDraw.pas на сопровождающем CD-ROM. Для того, чтобы получить доступ к DirectDraw, просто добавьте DDraw в предложение uses.

DirectDraw

DirectDraw может оказаться довольно каверзным в использовании. На первый взляд он кажется простым; существует только несколько СОМ-классов и они не имеют большого количества методов. Однако DirectDraw использует записи для определения всех видов различных параметров при создании своих объектов. На первый взгляд они выглядят действительно устрашающе. Вы можете найти их в справочных файлах Game SDK, начиная с букв DD, например DDSurfaceDesc. Являясь API низкого уровня, существует множество опций и параметров, которые допускают разницу в спецификациях аппаратного обеспечения и возможностях. К счастью, в большинстве случаев можно проигнорировать множеством этих опций. Самой большой проблемой в момент написания этой книги является недостаток информации в GDK документации, которая описывает, какие комбинации опций разрешаются, поэтому для того, чтобы помочь вам найти путь через минное поле, эта глава поэтапно проходит по всем стадиям создания приложения DirectDraw. Я представляю код, который добавляется на каждом этапе и использует его для объяснения аспекта DirectDraw, также как и рабочий пример, на основании которого можно строить свои собственные программы.

Объект IDirectDraw

DLL с DirectDraw фактически имеет самый простой из интерфейсов. Она экспортирует только одну функцию: DirectDrawCreate. Вы используете эту функцию для создания СОМ-объекта IDirectDraw, который открывает остальную часть API. Таким образом, первое, что должен сделать пример - создать один из таких объектов. Вы делаете это в обработчике события OnCreate формы и разрушаете его в OnDestroy. Лучшим местом хранения объекта является приватное поле главной формы. Листинг 1 содержит базовый код для осуществления этого.

Листинг 1 Создание объекта IDirectDraw.

unit Uniti;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, DDraw;

type
  TFormI = class (TForm)
    procedure FormCreate (Sender: TObject);
    procedure FormDestroy (Sender: TObject) ;
  private
    DirectDraw : IDirectDraw ; // главный объект DirectDraw
end;

var
  Formi: TFormI;

implementation

procedure TFormI. FormCreate (Sender: TObject);
begin
  { создать СОМ-объект DirectDraw }
  if DirectDrawCreate( nil, DirectDraw, nil ) <> DD_OK then
    raise Exception. Create ( 'Failed to create IDirectDraw object' ) ;
end;

procedure TFormI. FormDestroy (Sender: TObject);
begin
  { создать СОМ-объект DirectDraw эа счет вызова его метода Release }
  if Assigned ( DirectDraw ) then
    DirectDraw. Release ;
end;

end.

Вы можете скачать этот тривиальный пример DDDemo1 здесь (пока не можете, я надеюсь это будет позже, прим. Lel). Он не делает что-либо очевидного, когда вы запускаете его, поэтому не ожидайте слишком многого. Я включаю его для того, чтобы показать, как мало кода требуется для создания и освобождения СОМ-объекта DirectDraw. Это действительно очень просто.

Коды возврата DirectDraw и исключения Delphi

Подавляющее большинство функций DirectDraw возвращает результирующий код целого типа с именем HResult, о котором вы можете думать как об integer. Файл DDraw.pas имеет все возможные константы ошибок, занесенные в список, а справочный файл Game SDK указывает на возможные коды ошибки, возвращаемые каждой функцией. Вы можете проверить результаты этих функций, и в болыпинстце случаев возбудить исключение, если результат отличается от DD_OK.

Однако имеется ряд проблем с использованием исключений, поскольку вы переключаетесь на специальный экранный режим. Это означает, что вы не способны видеть Delphi IDE, когда он разрушается или прерывается в момент исключения, и ваша программа кажется замороженной. Установка точки прерывания обычно приводит в результате к одной и той же проблеме: приложение останавливается как раз в точке прерывания, но вы не имеете возможность увидеть Delphi. Добро пожаловать в программирование игр в среде Windows! Я обсуждаю это более подробно несколько позже.

Переключение на полноэкранный режим

Следующее, что необходимо сделать, - это переключить дисплей в режим перелистывания страниц. Когда вы это делаете, становится видимым только ваше приложение. Оно занимает весь экран. Любые другие приложения Windows, которые находятся ц режиме выполнения, подобные Windows Explorer, продолжают работать и могут записывать выходные данные на то, что они считают экраном. Вы не видите, как выглядят выходные данные, потому что другие приложения все еще используют GDI для выходных данных, которому ничего не известно о DirectDraw. Но вам вовсе нет необходимости беспокоиться об этом. GDI будет продолжать беспечно писать в экранную память, хотя вы вдействительности не сможете увидеть его выходные данные.

Путем переключения в специальный режим отображения данных вы занимаете весь экран. Как правило, вы можете запускать множество регулярных приложений среды Windows в одно и то же время; их окна перекрываются и благодаря GDI дела идут прекрасно. Но что произойдет, если вы попытаетесь запустить два и более полноэкранных DirectDraw-приложений в одно и то же время? Ответ - только одному разрешен доступ к полному экрану. DirectDraw управляет этим, предполагая, что вы имеете исключительный доступ к экранной карте перед изменением режима. Вы сделаете это установкой коиперативнчгн уровня объекта DirectDraw в Exclusive. DirectDraw поддерживает эксклюзивный уровень доступа только для одного приложения одновременно. Если вы попытаетесь получить эксклюзивный доступ и какое-нибудь другое приложение уже его имеет, вызов не удастся. Подобным же образом, если вы попытаетесь изменить режимы отображения данных без приобретения эксклюзивного доступа, этот вызов не удастся. Таким образом, попытайтесь получить эксклюзивный доступ и затем переключите режимы отображения.

Здесь необходимо отметить, что вы должны предоставить описатель окна SetCooperativeLevel. DirectDraw изменяет размеры этого окна автоматически, так что оно заполняет экран в новом режиме отображения данных. Вы должны передать описатель формы в SetCooperativeLevel. Ввиду того, что описатель окна не был создан до времени вызова OnCreate, вы должны все это сделать и событии OnShow. Листинг 2 показывает, как это сделать.

Листинг 2 Переключение в полноэкранный режим в OnShow.

procedure TForml.FormShow(Sender: TObject);
begin
  if DirectDraw.SetCooperativeLevel(Handle,
  DDSCI_EXC: LUSIVE or DDSCI_FUbbSCREEN ) <> DD_OK then
    raise Exception.Create('Unable to acquire exclusive full-screen access');

  if DirectDraw.SetDisplayMode(640, 480, 8) <> DD_OK then
    raise Exception.Create('Unable to set new display mode');
end;

Пока все в порядке. Ничего тут сложного нет. Если вы запустите пример прямо сейчас, ваше приложение переключит режимы и вы увидите, как форма заполнит экран. Если вы передвинете или измените ее размеры, вы увидите за ним Delphi. Вы все еще смотрите на поверхность вывода GDI. GDI может благополучно выводить данные в этих различных режимах, так что вы увидите свои обычные приложения Windows так долго, сколько эта поверхность будет оставаться на переднем плане. Но ввиду того, что вы создаете приложение с мелькающими страницами, это не совсем то, что нам нужно. Директория DDDemo2 содержит изложенные примеры

Добавление обработчика исключений приложения

Как я уже упоминал ранее, тот факт, что DirectDraw занимает полный экран может вызвать проблему с обработкой исключений. Когда исключение возбуждается, по умолчанию Delphi IDE попадает в отладчик программы и приостанавливает исполнение программы, устанавливая кодовое окно на строке, содержащей ошибку. Проблема заключается в том, что когда происходит мелькание страниц вы, вероятно, не сможете увидеть IDE и приложение будет выглядеть замороженным. Еще хуже, если вам удастся продолжить исполнение, или на опции IDE окажется выключенной Break on exception (Останавливаться, если возбуждено исключение), то вы можете не увидеть окна сообщения, которое появляется с сообщением исключения.

Один из способов избежать этот сценарии отменить маркер на флажке Break on exception в IDE (TooIsjOptions menu) и установить в своем приложении специальный обработчик исключений приложения. Этот обработчик должен переключаться на поверхность GDI перед тем, как показать сообщение исключения. Это намного легче, чем может показаться. Все, что вам необходимо сделать, - создать собственный private-метод в форме и присвоить его AppHcation,OnException в OnCreate формы. Не забывайте установить его обратно в nil в OnDestroy. Новый описатель может использовать метод SwitchToGDISurface объекта IDirectDraw перед вызовом MessageDIg. Листинг 3 показывает обработчик исключения.

Листинг 3 Обработчик исключений приложения.

procedure TForml.ExceptionHandler(Sender: TObject; E: Exception);
begin
  if Assigned(DirectDraw) then
    DirectDraw.FlipToGDISurface;
  MessageDIgt E.message, mtError, [mbOK], 0);
end;

Для того, чтобы устаноцить описатель исключения мы добаиим следующую строку в OnCreate:

Application.OnException := ExceptionHandler;

Помните, что нужно выключить Break on exception (в TooIsfOptions). Как только вы наберетесь больше опыта, вы сможете включить эту опцию снова для специфических заданий отладки. Однако, если ваше приложение вызовет исключение, пока поверхность GDI невидима, IDE возьмет свое и вы ничего не увидите. Нажатие F9 должно вызвать повторное исполнение, а нажатие Ctrl-F2 вернет приложение в исходное состояние и возвратить вас в Delphi.


DirectX (Игровой SDK) 2




Поверхности отображения

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

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

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

В этой связи я должен затронуть вопрос сложности DirectDraw, поскольку необходимо заполнять поля и записи TDDSurfaceDesc. Если вы прочитаете об этом и справке DirectDraw, вы сможете увидеть, что все это выглядит довольно ужасно! Но как я уже говорил, вы можете счастливо игнорировать большинство из этих полей. Листинг 4 представляет код, который необходимо добавить в обработчик OnShow для создания комплексной поверхности.

Листинг 4 Создание комплексной поверхности.

{ заполнить описатель DirectDrawSurface перед созданием поверхности }
FillChar(DDSurfaceDesc, Si2e0f(DDSurfaceDesc), 0);
with DDSurfaceDesc do
begin
  dwSize := SizeOf(DDSurfaceDesc);
  dwFlags := DDSD_CAPS or DDSD_BACKBUFFERCOUNT;
  ddSCaps.dwCaps := DDSCAPS_COMPLEX or DDSCAPS FLIP or

  DDSCAPS_PRIMARYSURFACE;
  dwBackBufferCount: = 1;
end;

Листинг 7 Тестирование нажатия клавиш Escape и F12.

procedure TForml.ForinKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  // если нажаты клавиши Escape или F12, завершить приложение
  case Key of
    VK_ESCAPE, VK_F12: Close;
  end;
end;

Вы можете скачать этот пример u DDDemo3 здесь. Если вы запустите его, иы уиидите на экране мелькание сменяющих друг друга поверхностей GDI, которые содержат формы размером с экран, и фоновый буфер, который, вероятно, заполнен различными битами "остатков" отображения. Помните, для выхода следует нажать Esc или F12 (или конечно же Alt+F4).

Получение доступа к фоновому буферу

Теперь, когда вы познали основы приложения смены страниц, вы, вероятно, захотите что-нибудь с ним сделать. Вы нуждаетесь в том, чтобы иметь возможность рисовать на поверхности фонового буфера. Однако, в последней секции вы создали комплексную поверхность, которая автоматически создала для нас фоновый буфер. Проблема заключается в том, что функция CreateSurface заполнила поле PrimaryField (основная поверхность), и вы должны получить доступ к фоновому буферу. Для этого можно вызвать метод GetAttachedSurface. Добавьте поле фонового буфера BackBuffer к форме и код из листинга 8 - к OnShow:

Листинг 8 Доступ к поверхности фонового буфера.

{ получить фонов зй буфер }
DDSCaps.dwCaps: = DDSCAPS_BACKBUFFER;

if PrimarySurface.GetAttachedSurface(DDSCaps, BackBuffer) <> DD_OK then
  raise Exception.Create('Failed to get back buffer surface');

DDSCaps является локальной переменной типа TDDSCaps, которая добавляется к обработчику FormShow. Вы заполняете флажки для необходимой присоединенной поверхности и вызываете GetAttachedSurface. В этом случае вам необходим фоновый буфер. Метод может вернуть только одну поверхность. Вызов напрасен, если более чем одна присоединенная поверхность соответствует переданным флажкам DDSCaps. Однако, не имеет значения, сколько фоновых поверхностей вы создали, существует только одна поверхность с флажком фонового буфера, и она является первой в цепи сменных поверхностей после основной. Если необходимо получить все присоединенные поверхности, можно вызвать функцию EnumAttachedSurfaces.

Восстановление поверхностей

Одна из многих особенностей DirectDraw заключается в том, что поверхности могут потерять свою память по многим причинам; например, когда изменяется режим отображения. Если это происходит, вы должны вызвать метод Restore поверхности, чтобы получить свою память обратно. Вы также должны перерисовать поверхность. Это несколько напоминает то, как у вас возникает необходимость нарисовать окно в обычном программировании для Windows, когда оно перекрывается и нуждается в обновлении. Большинство из функций IDirectDrawSurface могут возвратить результат DDERR_SLIRFACELOST. Когда это происходит, вы должны восстановить поверхность и перерисовать ее. Многие из этих функций также могут вернуть DDERR_WASSTILLDRAWING, что по сути означает, что аппаратное обеспечение занято и запрос необходимо повторять до тех пор, пока вы не добьетесь успеха, или пока вы не получите иное сообщение об ошибке.

Вот основополагающая логическая схема, использующая метод Flip. Этот пример предназначен только для того, чтобыввести вас в курс. Он не перерисовывает поверхности. Смотрите листинг 9.

Листинг 9 "Традиционный" код для проверки и восстановления поверхности.

repeat
  DDResult := PrimarySurf асе.Flip(nil, 0);
  case DDResult of
    DD_OK: break;
    DDERR_SURFACELOST:
      begin
        DDResult := PrimarySurface.Restore();
        if DDResult <> DD_OK then
          break;
      end;
  else
  if DDResult <> DDERR_WASSTILLDRAWING then
    break
  end;
until false;

Самое надоедливое то, что вам необходим подобный код практически для каждого вызова метода IDirectDrawSurface. Всякий раз, когда спецификация вызова в справке Game SDK содержит DERR_SLJRFACELOST в качестве возможного результата, это необходимо. Но Pascal-структурированный язык высокого уровня, не так ли? Таким образом, почему бы не написать небольшой сервисный метод для оказания такой помощи? Вот этот метод с именем одного из моих любимых шоу. (Оно не дает возможности себя забыть!) Оно представлено в листинге 10.


DirectX (Игровой SDK) 3




Листинг 10 функция MakeltSo для оказание помощи в восстановлении поверхности.

function TForinl.MakeltSo(DDResult: HResult): boolean;
begin
  { утилита для предоставления помощи в восстановлении поверхностей }
  case DDResult of
    DD_OK: Result := true;
    DDERR_SURFACELOST: Result := RestoreSurfaces <> DD_OK;
  else
    Result := DDResult <> DDERR_WASSTILLDRAWING;
  end;
end;

Последний метод иосстанаиливает поцерхность i3 случае необходимости и затем вызывает функцию RestoreSurface, которую я вам сейчас представлю. Но сначала вот как следует ее использовать, применяя Flip, как в предыдущем примере:

repeat
  ...
until
  MakeltSo(PrimarySurf асе.Flip(nil, DDFblP_WAIT));

Теперь я уверен, вы согласитесь, что это намного аккуратней и приятней, чем постоянно дублировать код, который я продемонстрировал ранее. Flip вызывается непрерывно, пока не достигнет успеха, либо пока не возникнет серьезная про блема. Я мог бы вызвать исключение в MakeltSo, если бы возникла неисправимая проблема. Примеры Game SDK, будучи написанными на С без обработки исключений, просто игнорируют результаты ошибки. Однако, если вы хотите использовать исключения, измените MakeltSo, как показано в листинге 11.

Листинг 11 Необязательная MakeltSo, которая вызывает исключения.

function TFormI.MakeltSo(DDResult: HResult): boolean;
begin
  { утилита для предоставления помощи в восстановлении
  поверхностей - версия с исключениями }
  Result := false;
  case DDResult of
    DD_OK: Result := true;
    DDEKR_SURFACELOST: if RestoreSurfaces <> DD_OK then
        raise Exception.Create('MakeltSo failed');
  else if DDResult <> DDERR_WASSTILLDRAWING then
    raise Exception.Create('MakeltSo failed');
  end;
end;

Хорошо, теперь перейдем к методу RestoreSurfaces, при необходимости вызываемому в MakeltSo. Листинг 12 показывает метод RestoreSurfaces.

Листинг 12 Восстановление и перерисовка поверхности DirectDraw.

function TFormI.RestoreSurfaces: HResult;
begin
  { вызывается MakeltSo, если поверхности "потерялись" -
  восстановить и перерисовать их }
  Result := PrimarySurface.Restore;
  if Result = DD_OK then
    DrawSurfaces;
end;

Ничего удивительного. Вызывается метод Restore объекта основной поверхности. Ввиду того, что вы создали ее как комплексный объект, он автоматически восстанавливает любые неявные поверхности. Поэтому нет необходимости вызывать Restore для фонового буфера. Если Restore успешно восстановил память поверхности, вы вызываете DrawSurfaces, которую мы обсудим подробно далее.

Рисование на поверхностям DirectDraw

Существует два способа рисовать на поверхности DirectDraw. Вы можете получить указатель непосредственно на область памяти поверхности и непосредственно ею манипулировать. Это очень мощный способ, но требует написания специального кода и часто для скорости - на ассемблере. Все-таки вам редко придется это делать, потому что DirectDraw может создавать контекст устройства (DC), совместимый с GDI. Это означает, что вы можете рисовать на ней, используя стандартные вызовы GDI, а также любой DC. Однако, вызовы GDI достаточно утомительны, и Delphi уже включает DC в свой класс TCanvas. Таким образом, в примере я создаю TCanvas и использую его для облегчения себе жизни. Разве невозможно полюбить Delphi за это!

Все, что необходимо сделать, - создать объект TCanvas и вызвать метод GetDC поверхности. Затем вы назначаете DCCanvas.Handle, убедившись, что вы по завершению переустановили Handle в ноль. Создание полотна и размещение контек- стов устройств требует памяти и ресурсов. Контексты устройства представляют собой особенно скудный ресурс. Существенно важно освободить их, когда вы закончите. Для того, чтобы сделать код непробиваемым, используйте блоки try...finally.

Листинг 13 представляет этот код. Он просто заполняет основную поверхность голубым цветом и выводит текст "Primary surface" (Основная поверхность) в центре слева. Фоновый буфер закрашивается в красный цвет и содержит текст "Back buffer" (Фоновый буфер) в центре справа. Листинг 13 с примером DDDemo4 можно скачать здесь.

Листинг 13 Данная процедура заполняет основную поверхность голубым цветом и выводит текст "Primary surface" (Основная поверхность) в центре слева. Фоновый буфер закрашивается в красный цвет и содержит текст "Back buffer" (Фоновый буфер) в центре справа.

procedure TForm1.DrawSurfaces;
var
  DC: HDC;
  ARect: TRect;
  DDCanvas: TCanvas;
  ATopPos: integer;
begin
  // fill the primary surface with red and the back buffer with blue
  // and put some text on each. Using a canvas makes this trivial.
  DDCanvas := TCanvas.Create;
  try
    // first output to the primary surface
    if PrimarySurface.GetDC(DC) = DD_OK then
    try
      ARect := Rect(0, 0, 640, 480);
      with DDCanvas do
      begin
        Handle := DC; // make the canvas output to the DC
        Brush.Color := clRed;
        FillRect(ARect);
        Brush.Style := bsClear; // transparent text background
        Font.Name := 'Arial';
        Font.Size := 24;
        Font.Color := clWhite;
        ATopPos := (480 - TextHeight('A')) div 2;
        TextOut(10, ATopPos, 'Primary surface');
      end;
    finally
      // make sure we tidy up and release the DC
      DDCanvas.Handle := 0;
      PrimarySurface.ReleaseDC(DC);
    end;

    // now do back buffer
    if BackBuffer.GetDC(DC) = DD_OK then
    try
      with DDCanvas do
      begin
        Handle := DC; // make the canvas output to the DC
        Brush.Color := clBlue;
        FillRect(ARect);
        Brush.Style := bsClear; // transparent text background
        Font.Name := 'Arial';
        Font.Size := 24;
        Font.Color := clWhite;
        TextOut(630 - TextWidth('Back buffer'), ATopPos, 'Back buffer');
      end;
    finally
      // make sure we tidy up and release the DC
      DDCanvas.Handle := 0;
      BackBuffer.ReleaseDC(DC);
    end;
  finally
    // make sure the canvas is freed
    DDCanvas.Free;
  end;
end;

Непригодность основной формы

В предыдущих примерах форма была явно видима, заполняя собой основную поверхность. Однако, вы не хотите, чтобы пользователь видел форму. Это приложение смены страниц и оно рисует по всему экрану. Поэтому вы должны предотвратить отображение формы на экране. Также необходимо избавиться от системного меню и неклиентских клавиш. Все это можно достичь просто установкой BorderStyle формы в bsNone в методе Foi-rnCreate. Вам также не нужен и курсор, поэтому установите его в crNone. Добавьте эти три строки к FormCreate:

BorderStyle := bsNone;
Color := clBlack;
Cursor := crNone;

Единственно, что остается сделать, - убедиться ц том, что поверхности рисуются правильно и самом начале. Сделайте проверку, вызвав DrawSurfaces в обработчике события OnPaint формы. Если вы этого не сделаете, основная поверхность изначально отобразит форму; то есть, экран будет полностью черным. Листинг 14 представляет обработчик события OnPaint формы.

Листинг 14 Обработчик события OnPaint просто вызывает DrawSurfaces.

procedure TForml.FormPaint(Sender: TObject);
begin
  // рисовать что-нибудь на основной поверхности и на фоновом буфере
  DrawSurfaces;
end;

Ну, все! Вы можете найти измененный код в примере DDDemo4(скачать).

Мощь Delphi: пользовательский класс полотна (Canvas)

До этого вы наблюдали, как использовать прекрасное средство Delphi TCanvas для получения доступа к контексту устройства, который позволяет рисовать на поверхности DirectDraw. Однако, мы можем значительно все упростить благодаря применению объектной ориентации. Сейчас вы создадите специализированный (пользовательский) подкласс TCanvas для того, чтобы иметь возможность рисовать на поверхности даже намного проще. Это очень просто; код представлен в листинге 15.

Листинг 15 Объект полотна DirectDraw в Delphi.

unit DDCanvas;

interface

uses Windows, SysUtils, Graphics, DDraw;

type
  TDDCanvas = class(TCanvas)
  private
    FSurface: IDirectDrawSurface;
    FDeviceContext: HDC;
  protected
    procedure CreateHandle; override;
  public
    constructor Create(Asurface: IDirectDrawSurface);
    destructor Destroy; override;
    procedure Release;
  end;

implementation

constructor TDDCanvas.Create(Asurface: IDirectDrawSurface);
begin
  inherited Create;
  if Asurface = nil then
    raise Exception.Create('Cannot create canvas for NIL surface');
  FSurface := Asurface;
end;

destructor TDDCanvas.Destroy;
begin
  Release;
  inherited Destroy;
end;

procedure TDDCanvas.CreateHandle;
begin
  if FDeviceContext = 0 then
  begin
    FSurface.GetDC(FDeviceContext);
    Handle := FDeviceContext;
  end;
end;

procedure TDDCanvas.Release;
begin
  if FDeviceContext <> 0 then
  begin
    Handle := 0;
    FSurface.ReleaseDC(FDeviceContext)
      FDeviceContext := 0;
  end;
end;

end.



DirectX (Игровой SDK) 4




Использование класса DDCanvas.

Для того, чтобы использопать этот класс, следует скопировать модуль DDCanvas.pas ц каталог Lib, который находится в каталоге Delphi 3.0, или и другой каталог, обозначенный в пути поиска библиотеки.

Помните ли вы злополучное взаимное исключение Win, которое приостанаилипает многозадачную работу? Хорошо, я еще раз подчеркну необходимость освобождения DC. Класс TDDCanvas имеет и использует в своих целях метод Release. Всегда заворачивайте любой доступ к полотну в блок try..finally, например:

try
  DDCanvas.TextOut(0, 0, 'Hello Flipping World!');
  {и т.д. }
finally
  DDCanvas.Release;
end;

Или, как я часто делаю, используйте конструкцию with для того, чтобы сэкономить время набора:

with DDCanvas do
  try
    TextOuK 0, 0, 'Hello Withering World!');
    {и т.д. }
  finally
    Release;
  end;

Итак, теперь вы можете добавить пару таких полотен в объявления формы, создавая их в FormShow, например:

{ создать два TDDCanvas для наших двух поверхностей }
PrimaryCanvas := TDDCanvas.Create(PrimarySurface);
BackCanvas := TDDCanvas, Create(BackBuffer);

Освободите их в FormDestroy перед тем, как освободить поверхности:

{ освободить объекты TDDCanvas перед освобождением поверхностей }
PrimaryCanvas.Free;
BackCanvas.Free;

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

Листинг 16 DrawSurfaces использует объекты TDDCanvas.

procedure TFormI.DrawSurfaces;
var
  ARect: TRect;
  ATopPos: integer;
begin

  // вначале выводить на основную поверхность.
  ARect := Rect(0, 0, 640, 480);
  with PrimaryCanvas do
  try
    Brush.Color;
    = cIRed;
    FillRect(ARect);
    Brush.Style: <= bsClear;
    Font.Name: = ' Arial ';
    Font.Size := 24;
    Font.Color := clWhite;

    ATopPos := (480 - TextHeight('A')) div 2;
    Text0ut(10, ATopPos, 'Primary surface');
  finally

    // убедиться, что мы сразу же освободили DC,
    // поскольку Windows замораживается, пока мы владеем DC.
    Release;
  end;

  // теперь работаем с фоновым буфером
  with .BackCanvas do
  try
    Brush.Color: = clBlue;
    FillRecK ARect);
    Brush, Style := bsClear;
    Font.Name := 'Arial';
    Font.Size. = 24;
    Font.Color := clWhite;

    Text0ut(630 - TextWidth('Back buffer'), ATopPos, 'Back buffer');

  finally
    // убедиться, что DC освобожден
    Release;
  end;
end;

Заметьте блоки try...finally с вызовом Release. Помимо этого, теперь пы добрались до этапа, на котором уже можно рисовать на поверхностях DirectDraw, не используя скверные коды DirectDraw, а просто приятные методы полотна Delphi!

Улучшение нашего изображения

Теперь, когда у вас прекрасно работает смена страниц, самое время научиться загружать растровое изображение на поверхность отображения. Процесс загрузки растрового изображения значительно упрощен по сравнению с тем, как это происходило в Windows 3.х, за счет введения функций Loadimage и CreateDIBSection а WIN32 API. В Windows 95 вы можете использовать Loadimage для загрузки растрового изображения либо с дискового файла, либо из ресурса. В окончательном приложении вы несомненно встроите свои изображения в ЕХЕ-файл в виде ресурсов. Однако, полезно иметь возможность загружать их из файла во время разработки.

Первой из них, на которую следует обратить внимание, является DDReLoadBitmap. Вы можете смело использовать ее без понимания того, что она делает, но с целью обучения полезно немного заглянуть в этот код. Бывают моменты, когда вам может понадобиться самостоятельно написать специализированный код по обслуживанию растровых изображений. Это даст вам определенное понимание того, как это сделать. Листинг 17 представляет эту процедуру.

Листинг 17 Сервисная процедура DDReLoadBitmap для загрузки изображений.

procedure DDReLoadBitmap(Surface: IDirectDrawSurface; const BitmapName: string);
var
  Bitmap: HBitmap;
begin
  // попытаться загрузить изображение как ресурс;
  // если это не удается, то как файл
  Bitmap := Loadimage(GetModuleHandle(nil), PChar(BitmapName),
    IMAGE__BITMAP, 0, 0, LR_CREATEDIBSECTION);
  try
    if Bitmap = 0 then
      Bitmap := Loadimage(0, PChar(BitmapName), IMA.GE_BITMAP,
        0, 0, LR_LOADFROMFILE or LR_CREATEDIBSECTION);
    if Bitmap = 0 then
      raise Exception.CreateFmt('Unable to load bitmap Is', [BitmapName]);
    DDCopyBitmap(Surface, Bitmap, 0, 0, 0, 0);
  finally
    DeleteObject(Bitmap);
  end;
end;

Вы указываете в DDReLoadBitmap поверхность DirectDraw и имя растрового изображения, которое вы хотите загрузить в поверхность. Процедура сначала попытается произвести загрузку из ресурса, предполагая, что BitmapName является именем ресурса. Если это не удается, она предполагает, что вы указали имя файла и попытается загрузить его из файла. На самом деле в этом случае при помощи Loadimage создается секция DIB. Это Hbitmap из Windows с форматом аппаратно независимого растрового изображения (DIB). Вы можете использовать DIB-секцию как обычный Hbitmap, например, выбрав ее для DC и вызвав стандартную функцию GDI BitBIt.

DDReLoadBitmap вызывает другую сервисную программу - DDCopyBitmap, которая копирует изображение секции DIB на поверхность DirectDraw. Затем блок try...finally избавляется от секции DIB, поскольку она больше не нужна. В отличие от кода обеспечения растровых изображений Windows 3.х, эта процедура достаточно проста. Теперь, как по поводу DDCopyBitmap? Как показано в листинге 18, это не намного сложнее.

Листинг 18 Сервисная процедура для копирования растрового изображения на поверхность.

procedure DDCopyBitmap(Surface: IDirectDrawSurface; Bitmap: HBITMAP;
  х, y.Width, Height: integer);
var
  ImageDC: HDC;
  DC: HDC;
  BM;
  Windows.TBitmap;
  SurfaceDesc: TDDSurfaceDesc;
begin
  if (Surface = nilor (Bitmap = = 0) then
    raise Exception.Create('Invalid parameters for DDCopyBitmap');
  // убедиться, что поверхность восстановлена.
  Surfасе.Restore;
  // выбрать изображение для memoryDC, чтобы его использовать.
  ImageDC: = CreateCompatibleDC(0);
  try
    Select0bject(ImageDC, Bitmap);
    // получить размер изображения.
    Get0bject(Bitinap, Size0f(BM), @BM);
    if Width = 0 then
      Width := = BM.bmWidth;
    if Height = = 0 then
      Height := = BM.bmHeight;
    // получить размер поверхости.
    SurfaceDesc.dwSize := SizeOfC SurfaceDesc);
    SurfaceDesc.dwFlags := DDSD_HEIGHT or DDSDJWIDTH;
    Surf ace.GetSurfaceDesc(SurfaceDesc);
    if Surf ace.GetDC(DC) <> DD_OK then
      raise Exception.Create('GetDC failed for DirectDraw surface' )
    try
      StretchBlt(DC, 0, 0, SurfaceDesc.dwWidth, SurfaceDesc.dwHeight,
      ImageDC, x, y.Width, Height, SRCCOPY);
    finally
      Surface.ReleaseDC(DC);
    end;
  finally
    DeleteDC(ImageDC);
  end;
end;

После проверки некоторых параметров DDCopyBitmap вызывает Restore, чтобы обеспечить корректность память поверхности, Затем она обращается к обычной программе Windows для копирования растрового изображения с одного DC на другой. Исходное растровое изображение выбирается для первого DC, стандартная память DC обеспечивается вызовом CreateCompatibleDC. Передача нулевых параметров ширины и высоты в программу заставляет использовать фактическую ширину и высоту растрового изображения. Для того, чтобы получить эту информацию, программа использует функцию GetObject

Затем заготавливается запись SurfaceDesc путем включения флажков DDSD_HEIGHT и DDSD_WIDTH. Это передает ся в GetSurfaceDesc, которое реагирует путем заполнения полей dwHeight и dwWidth дескриптора. Программа получает второй DC из поверхности, используя вызов GetDC и осуществляя простое StretchBIt Как обычно, блоки try..-Anally используются для обязательного освобождения DC. Все это довольно простые вещи. Это развеивает по ветру устаревшую истину о том, что код обработки растровых изображений для Windows тяжело писать. К счастью, теперь вы сможете прибегнуть к сочинению подобного кода без чувства опасения за будущее!

Kод DrawSurface упрощается еще больше, потому что фоновый буфер теперь можно загружать где угодно, используя DDReLoaBitmap. Упрощенный DrawSurface представлен в листинге 19.

Листинг 19 DrawSurface без кода отрисовки фоновой поверхности.

procedure TFormI DrawSurfaces;
var
  ARect: TRect;
  ATopPos: integer;
begin
  // вывод на основное полотно.
  ARect := Rect(0, 0, 640, 480);
  with PrimaryCanvas do
  try
    Brush.Color := clBlue;
    FiliRect(ARect);
    Brush.Style := = bsClear;
    Font.Name := = 'Arial';
    Font.Size := = 24;
    Font.Color := clWhite;
    ATopPos: ^(480 - TextHeight('A' ) ) div 2 ;
    Text0ut(10, ATopPos, 'Primary surface');
  finally
    // убедиться, что мы сразу же освободили DC,
    // поскольку Windows замораживается, пока мы владеем DC.
    Release;
  end;
  { загрузить изображение в фоновый буфер }
  DDReloadBitmap(BackBuffer, GetBitiilapName);
end;

А что по поводу палитр?

Я знал, что об этом вы обязательно бы меня спросили! Хорошо, мы все еще вынуждены работать с палитрами. Настало время представить еще один СОМ-объект DirectDraw, На этот раз это lDirectDrawPalette. Этот маленький полезный объект обслужит большинство компонент палитры, нс утруждая этим нас с вами. Для того, чтобы использовать IDirectDraw, высоздаете его с IDirectDraw.CreatePalette, которая устанавливает указатель на массив вводимых данных палитры, который использовался для инициализации объекта палитры. Затем вы присоединяете ее к поверхности DrawSurface и она станет использоваться автоматически для всех последующих операций. Конечно же, прекрасно.

Итак, как же получить эти значения цветов? Хорошо, я написал еще одну небольшую функцию для их загрузки из растрового изображения или создания цветов по умолчанию, и для создания и возврата объекта IDirectDrawPalette. Она также находится в DDUtils.pas и называется DDLoadPalette. Вы просто передайте ей имя вашего объекта IDirectDraw либо с именем растрового изображения, либо (если вы хотите палитру по умолчанию) с пустой строкой. (Как и другие программы, DDLoadPalette сначала пытается загрузить растровое изображение из ресурса приложения. Если это не удается, она пытается загрузить растровое изображение из файла. Я не повторяю здесь код, поскольку он несколько длиннее других функций. Он главным образом имеет дело с проверкой наличия у DIB таблицы цветов, которую он затем копирует в массив вводимыхданных палитры).

Я добавил объект палитры к объявлению формы, загрузил его в FormShow и присоединил объект палитры к основной поверхности следующим образом:

{ загрузить палитку иэ растрового изображения
и присоединить ее к основной поверхности }
DDPalette := DDLoadPalette(DirectDraw, GetBitmapName);
PrimarySurface, SatPalette(DDPalette);

Создав, вы должны освободить его из основной поверхности в FormDestroy:

{ освободить DD-палитру }
if Assigned(DDPalette) then
  DDPalette.Release;

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

Объединение всего вместе

В настоящий момент вы можете составить DirectDraw-приложсние со сменой страниц, а также загрузить растровое изображение и палитру. У вас имеется все необходимое для создания смены страниц и причем на полной скорости! Для того, чтобы было еще интересней, как насчет анимации? DirectDraw в одной из демонстрационных программ использует файл с именем ALL.BMP. Вы также скачать его вместе с примером DDDenno5. В ней содержится еще одно более интересное фоновое изображение и набор анимационных кадров с красным вращающимся трехмерным тором.

Перед очередной сменой страницы вы захотите отобразить фоновое изображение и затем текущий анимационный кадр с тором. Вы создаете три тора в разных позициях на экране, которые будут вращаться с разной скоростью. Ввиду того, что фоновый буфер будет непрерывно перерисовываться, вы должны хранить где-нибудь еще исходное изображение, загруженное из ALL.BMP. Поэтому создайте для него еще одну поверхность DirectDraw. Это внеэкранная плоскость и она не имеет отношения к смене страниц; на ней мы будем хранить изображение.

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

Битовый перенос (bit-blitting) - термин, используемый для описания переноса областей растровых изображений в, из или в пределах других растровых изображений. Термин иногда записывается более точно как bitblting, но он сложен для чтения, поэтому вы часто найдете его в расчлененным в виде двух слов bit-blitting. BitBIt - краткое описание термина BITmap Block Transfer (перенос блока растрового изображения).

Итак, за работу. Создайте эту дополнительную поверхность и назовите ее Image (изображение). Добавьте ее в объявление формы. Это как раз и есть IDirectDrawSurface, поэтому нет необходимости представлять здесь эту тривиальную строку кода. Затем добавьте код в FormShow, который создает растровое изображение. Используйте DDLoadBitmap, это только одна строка! Вот она:

Image := DDLoadBitmap (Directdraw, GetBitmapName, О, О);

Помните, что вам необходимо пополнить метод RestoreSurfaces и тогда вы получите новую неявную поверхность. Если восстановление основной памяти поверхности пройдет нормально, попытайтесь восстановить поверхностную память Image. Если оба типа восстановлений будут иметь место, вызовите DrawSurfaces, как показано в листинге 20.

Листинг 20 Восстановление всех поверхностей.

function TFormI.RestoreSurfaces: HResult;
begin
  Result := primarySurface.Restore;
  if Result = DD OK then
  begin
    Result := Image - Restore;
    if Result = DD_OK then
      DrawSurfaces
  end;
end;


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