VR
Virtual Reality On-line   Журнал
Новости   |     Журнал    |    Хаkер    |     Магазин   |   Проекты
[   Вход    ]
[Kарта сайтa]

[ Download  ]
[  Конкурс  ]
[ Анекдоты  ]
[  Ссылки   ]
[  Реклама  ]
[ Почтальон ]
[ О проекте ]






TopList
Delphi+DirectX7,8.
Всё с самого начала
:
Logo

Эту рубрику я так же давно не делал, потому что это было связано с отсутствием времени. За всё это время я разобрался с новыми функциями DirectX7 и уже немного познакомился с восьмой версией. Поэтому я начинаю рассказ с самого начала, делая упор на новинки последних версий DirectX. И всё же желательно будет прочитать предыдущие мои статьи на эту тему.
Logo

Я слишком давно не писал про DirectX поэтому я решил начать с самого начала. Сначала я хотел продолжить свой рассказ, но всё же решил повторится. Да и повторение никогда и никому не мешало. На этот раз я уже не буду рассписывать все функции, а буду только показывать их в действии и давать небольшое описание.

Самым основным объектом в DirectX является IDirectDraw. С помощью него создаются все остальные необходимые объекты. Его инициализация должна проходить первой. Раньше для этого я использовал функцию DirectDrawCreate и интерфейс IDirectDraw. В седьмой версии появился обновлённый интерфейс IDirectDraw7 и DirectDrawCreateEx. Её объявление выглядит следующим образом:

 DirectDrawCreateEx : function(
  lpGUID: PGUID;
  out lplpDD: IDirectDraw7; 
  const iid: TGUID; 
  pUnkOuter: IUnknown
  ) : HResult; stdcall;

Раньше нам приходилось создавать интерфейс IDirectDraw, а потом запрашивать с помощью QueryInterface более новый интерфейс (например, IDirectDraw). В новой функции мы сразу получаем новый интерыейс из седьмой версии IDirectDraw7. Так что мы избавляемся от одной лишней операции.

Так как параметры функции изменились, я распишу её параметры:

  • lpGUID - используемый для вывода драйвер. Возможные значения: DDCREATE_HARDWAREONLY - использовать только аппаратные возможности; DDCREATE_EMULATIONONLY - использовать только эмуляцию; nil - использовать аппаратные возможности и эмуляцию.
  • IDirectDraw7 - IDirectDraw - указатель на объект IDirectDraw7, который будет инициализирован.
  • TGUID - запрашиваемый интерфейс. Указывая IDirectDraw7, ты будешь запрашивать 7-ю версию. Ты можешь указать и более старую, но не стоит обрезать себе (красивое выражение :)) новые возможности.
  • pUnkOuter - остался зарезервированым до лучших времён, обязательно должен быть nil.

Давай взглянём на процедуру FormCreate (обработчик события OnCreate) моего примера, в котором используется новая функция создания IDirectDraw:

procedure TForm1.FormCreate(Sender: TObject);
var
 hRet : HRESULT;
 SurfaceDesc : TDDSurfaceDesc2;
begin
 //Обнуляю переменные
 FPrimarySurface := nil;
 FDirectDraw := nil;

 //Создаю интерфейс FDirectDraw седьмой версии IDirectDraw7.
 hRet := DirectDrawCreateEx (nil, FDirectDraw, IDirectDraw7, nil);
 if hRet <> DD_OK then 
  begin
   ErrorOut(hRet);
   Close;
   Exit;
  end;

 //устанавливаем флаги используемого режима работы
 hRet := FDirectDraw.SetCooperativeLevel(Handle, DDSCL_FULLSCREEN 
     or DDSCL_EXCLUSIVE);
 if hRet <> DD_OK then
  begin
   ErrorOut(hRet);
   Close;
   Exit;
  end;

 //Устанавливаем режим экрана
 hRet := FDirectDraw.SetDisplayMode (640, 480, 16, 0, 0);
 if hRet <> DD_OK then
  begin
   ErrorOut(hRet);
   Close;
   Exit;
  end;

 //заполняю структуру SurfaceDesc необходимую при создании поверхностей
 FillChar (SurfaceDesc, SizeOf(SurfaceDesc), 0);
 SurfaceDesc.dwSize := SizeOf(SurfaceDesc);
 SurfaceDesc.dwFlags := DDSD_CAPS;
 SurfaceDesc.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE;

 //Создаю первичную померхность на основе параметров в SurfaceDesc
 hRet := FDirectDraw.CreateSurface(SurfaceDesc, FPrimarySurface, nil);
 if hRet <> DD_OK then
  begin
   ErrorOut(hRet);
   Close;
   Exit;
  end;

 //Загружаю битовый массив (картинка), который потом появится на экране.
 BkBitmap := TBitmap.Create;
 BkBitmap.LoadFromFile ('1.bmp');
end;

Здесь используется три переменные FDirectDraw, FPrimarySurface и BKBitmap, которые объявлены в разделе private объявления формы:

  private
    { Private declarations }
    FDirectDraw : IDirectDraw7;
    FPrimarySurface : IDirectDrawSurface7;
    BKBitmap : TBitmap;

А теперь разберём содержимое процедуры FormCreate. В самом начале я обнуляю переменные FPrimarySurface и FDirectDraw. Все переменные поверхностей и интерфейса DirectDraw должны быть обнулены перед использованием. Иначе можно получить ошибку.

Далее, инициализируется интерфейс DirectDraw с помощью новой функции DirectDrawCreateEx. Я запрашиваю интерфейс седьмой версии. После этого я устанавливаю флаги используемого режима работы с помощью функции SetCooperativeLevel. Она не изменилась и продолжает выглядеть в виде:

 function SetCooperativeLevel
  (hWnd: HWND; - указатель на окно.
  dwFlags: DWORD - флаги.
 ): HResult; stdcall;

Второй параметр - это флаги, которые могут быть сочетанием следующих доступных констант:

  • DDSCL_ALLOWMODEX - Позволяет использование ModeX режимов дисплея.
  • DDSCL_ALLOWREBOOT - Позволяет реагировать на CTRL_ALT_DEL во время полноэкранного исключительного режима.
  • DDSCL_EXCLUSIVE - Запрашивает исключительный уровень.
  • DDSCL_FULLSCREEN - Указывает, что исключительный владелец режима будет ответственен за всю основную поверхность. GDI может игнорироваться.
  • DDSCL_NORMAL-Указывает, что прикладная программа будет функционировать как обычная прикладная программа Windows.
  • DDSCL_NOWINDOWCHANGES -Указывает, что DirectDraw не позволяется минимизировать или восстановить окно прикладной программы при активации.

Я использую сочетание двух флагов DDSCL_EXCLUSIVE и DDSCL_FULLSCREEN, запрашивая полный экран и исключительный доступ к ресурсам.

Следующим этапом я выставляю видеорежим. Так как я запросил полноэкранное приложение, я смело могу менять парметры экрана с помощью SetDisplayMode:

 function SetDisplayMode(
  dwWidth, //Ширина экрана
  dwHeight, //Высота экрана
  dwBpp: DWORD; //Число бит на пиксел
  dwRefreshRate: DWORD;// Частота развёртки (0-по использовать умолчанию)
  dwFlags: DWORD// Зарезервирован, должен быть 0.
 ): HResult; stdcall;

Я узнал, что последний параметр оказывается не зарезервирован. Он может быть использован (есть флаги), но только для ModeX режимов. Если я не ошибаюсь, то это режимы 320 на 200. Я думаю, что ты не будешь пользоваться таким старьём. Поэтому я даже не упоминаю о флагах этого режима.

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

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

Создание поверхности происходит с помощью CreateSurface:

 function CreateSurface(
  const lpDDSurfaceDesc: TDDSurfaceDesc;//Параметры поверхности
  out lplpDDSurface: IDirectDrawSurface; //Указатель на создаваемую 
		                      //поверхность
  pUnkOuter: Iunknown //Зарезервирован, должен быть nil
 ): HResult;

Второй параметр вернёт созданную поверхность. Она будет создана на основе параметров указаных в сруктуре TDDSurfaceDesc первого параметра. Она немного изменилась и в начиная с шестой версии выглядит так:

  PDDSurfaceDesc_DX6 = ^TDDSurfaceDesc_DX6;
  TDDSurfaceDesc_DX6 = packed record
    dwSize: DWORD;       // размер структуры TDDSurfaceDesc
    dwFlags: DWORD;      // флаги, указывающие, какие поля заполнены
    dwHeight: DWORD;     // высота поверхности
    dwWidth: DWORD;      // ширина поверхности
    case Integer of
    0: (
      dwLinearSize : DWORD;       
     );
    1: (
      lPitch: LongInt;          // Расстояние до начала следующей линии
      dwBackBufferCount: DWORD; // Число дополнительных буферов
      case Integer of
      0: (
        dwMipMapCount: DWORD;   //Число уровней mipmap.
        dwAlphaBitDepth: DWORD; // Глубина альфа-буфера
        dwReserved: DWORD;      // Зарезервировано
        lpSurface: Pointer;     // Указатель на память связанной поверхности
        ddckCKDestOverlay: TDDColorKey;  // Цветовой ключ для оверлея примника
        ddckCKDestBlt: TDDColorKey;      // Цветовой ключ для Blt приёмника
        ddckCKSrcOverlay: TDDColorKey;   // Цветовой ключ для оверлея источника
        ddckCKSrcBlt: TDDColorKey;       // Цветовой ключ для Blt источника
        ddpfPixelFormat: TDDPixelFormat_DX6; // формат пиксела
        ddsCaps: TDDSCaps;                // возможности поверхности
       );
      1: (
        dwZBufferBitDepth: DWORD;      // Глубина z-буфера
       );
      2: (
        dwRefreshRate: DWORD;          // частота обновления
       );
     );
  end;

Заполнять все поля структуры не обязательно. Обязательным к заполнению является только размер структуры - dwSize. А в остальном, ты должен указать в поле dwFlags флаги параметров, которые ты хочешь указать сам. Остальные параметры будут взяты по умолчанию.

dwFlags может принимать значения:

  • DDSD_ALL - Все входные члены имеют силу.
  • DDSD_ALPHABITDEPTH - dwAlphaBitDepth имеет силу.
  • DDSD_BACKBUFFERCOUNT - dwBackBufferCount имеет силу.
  • DDSD_CAPS - ddsCaps имеет силу.
  • DDSD_CKDESTBLT - ddckCKDestBlt имеет силу.
  • DDSD_CKDESTOVERLAY - ddckCKDestOverlay имеет силу.
  • DDSD_CKSRCBLT - ddckCKSrcBlt имеет силу.
  • DDSD_CKSRCOVERLAY - ddckCKSrcOverlay имеет силу.
  • DDSD_HEIGHT - dwHeight имеет силу.
  • DDSD_LPSURFACE - lpSurface имеет силу.
  • DDSD_MIPMAPCOUNT - dwMipMapCount имеет силу.
  • DDSD_PITCH - lPitch имеет силу.
  • DDSD_PIXELFORMAT - ddpfPixelFormat имеет силу.
  • DDSD_REFRESHRATE - dwRefreshRate имеет силу.
  • DDSD_WIDTH - dwWidth имеет силу.
  • DDSD_ZBUFFERBITDEPTH - dwZBufferBitDepth имеет силу.

Я использовал только флаг DDSD_CAPS, чтобы указать тип необходимой мне поверхности. Я создаю только одну, первичную поверхность, чтобы можно было выводить на экран (SurfaceDesc.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE;).

Поле dwCaps, которое я заполняю выглядет в виде простой структуры:

 DDSCAPS 
  Typedef struct _DDSCAPS {
    DWORD dwCaps;
  } DDSCAPS, FAR * LPDDSCAPS;

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

После каждой операции DirectDraw я проверяю возвращаемое значение, сравнивая с константой DD_OK. Если значение не равно, то произошла ошибка. Я вывожу сообщение и заканчиваю приложение:

 if hRet <> DD_OK then
  begin
   ErrorOut(hRet);
   Close;
   Exit;
  end;

Ты можешь сравнивать не с константой DD_OK а с 0, потому что DD_OK=0. Хотя желательно использовать константу.

С инициализацей покончено. Итак, мы проинициализировали DirectDraw, установили необходимые параметры и создали первичную поверхность для ресования. Теперь мы готовы рисовать.

Но прежде чем объяснить тебе процесс рисования, я покажу, как уничтожаются интерфейсы DirectDraw. Это происходит в процедуре FormDestroy (Обработчик события OnDestroy):

procedure TForm1.FormDestroy(Sender: TObject);
begin
 if Assigned(FPrimarySurface) then FPrimarySurface := nil;
 if Assigned(FDirectDraw) then FDirectDraw := nil;
 BkBitmap.Free;
end;

Как видишь, уничтожение происходит в обратном порядке, простым присвоением переменным значения nil.

Теперь можно переходить к рисованию. Это происходит по событию OnPaint:

procedure TForm1.FormPaint(Sender: TObject);
var
 DC : HDC;
 TempCanvas : TCanvas;
 hRet : HRESULT;
 bltfx : TDDBLTFX;
begin
 //Обнуляю структуру bltfx
 ZeroMemory(@bltfx, SizeOf(bltfx));
 //Указываю её размер
 bltfx.dwSize := sizeof(bltfx);
 //Задаю цвет закрашивания (фона)
 bltfx.dwFillColor := 0;
 while True do
  begin
   //Использую функцию Blt поверхности для её закрашивания.
   hRet := FPrimarySurface.Blt(nil, nil, nil, 
       DDBLT_COLORFILL or DDBLT_WAIT, @bltfx);
   //Если произошла ошибка DDERR_SURFACELOST, значит поверхность потеряна
   if hRet = DDERR_SURFACELOST then
    begin
     //Пробую восстановить поверхность
     hRet := FPrimarySurface._Restore;
     if hRet <> DD_OK then Break;
    end 
   else Break;
  end;

 //Получаю контекст рисования поверхности. Это простой Win32 контекст
 hRet := FPrimarySurface.GetDC(DC);
 //Проверяю на ошибку
 if hRet=0 then
  begin
   //Если ошибки не было пытаюсь нарисовать загруженую картинку
   TempCanvas := TCanvas.Create;
   TempCanvas.Handle := DC;
   TempCanvas.Draw(95, 15, BkBitmap);
   TempCanvas.Free;
   FPrimarySurface.ReleaseDC (DC);
  end;
end;

Начну я не с начала, а с функции blt. Это функция относится к интерфейсу поверхности и выглядит следующим образом:

 function Blt (
  lpDestRect: PRect;   //Область приёмника.
  lpDDSrcSurface: IDirectDrawSurface;//Поверхность которая будет копироваться.
  lpSrcRect: PRect;    //область источника.
  dwFlags: DWORD;      //Флаги.
  lpDDBltFx: PDDBltFX  //Структура хранящая параметры копирования.
  ) : HResult; stdcall;

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

Последний параметр - структура типа TDDBLTFX, которую я тоже сейчас не буду описывать, а только скажу как она используется. В самом начале она должна быть заполнена нулями я для этого использую функцию ZeroMemory. Ты также должен обязательно заполнить поле dwSize, который должен содержать размер структуры. Я ещё использую поле dwFillColor, которое указывает на цвет, который будет использоватся в качестве фона при копировании. В это поле я заношу ноль, то есть чёрный цвет. В принципе, я мог бы не заполнять это поле, потому чт при обнулении оно тоже стало нулевым, но я это сделал, чтобы было понятней, что происходит.

Теперь несколько хитростей: если первый параметр (размер приёмника) равен нулю (nil, как в моём случае), то будет использоватся вся область приёмника. То же самое касается и источника.

Если второй параметр (поверхность для копирования) равна nil, то копирования не будет. Но почему же тогда я использую эту функцию? Здесь секрет в флагах копирования. У меня это DDBLT_COLORFILL и DDBLT_WAIT. Второй флаг указывает, что нужно дождатся момента, когда можно будет копировать. Если этот флаг не указан, а поверхность не готова к копированию, то произойдёт ошибка. А вот первый флаг DDBLT_COLORFILL указывает на то, что нужо производить копирование цвета из поля dwFillColor структуры TDDBLTFX. У меня там указан чёрный цвет, размеры приёмника не указаны, значит произойдёт копирование чёрного цвета на всю поверхность. Это маленький трюк, с помощью которого можно быстро закрасить всю поверхность одним цветом.

И на последок я открою ещё один секрет функции Blt. Если указать разные размеры источника и приёмника, то будет происходит масштабирование.

После копирования, я проверяю возвращённое значение на ошибку. Если она равна DDERR_SURFACELOST, то это значит, что первичная поверхность потеряна. Это происходит, когда ты переключишься на другое приложение, а потом вернёшся к этому приложению. Если произошла потеря поверхности, то я восстанавливаю её с помощью функции Resctore интерфейса поверхности FPrimarySurface._Restore.

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

Если всё прошло ничтяк, то я произвожу копирование загруженной картинки:

 hRet := FPrimarySurface.GetDC(DC);
 if hRet=0 then
  begin
   TempCanvas := TCanvas.Create;
   TempCanvas.Handle := DC;
   TempCanvas.Draw(95, 15, BkBitmap);
   TempCanvas.Free;
   FPrimarySurface.ReleaseDC (DC);
  end;

С помощью функции GetDC поверхности я получаю контекст рисавания FPrimarySurface.GetDC(DC). Это Win32 совместимый контекст, поэтому я могу использовать его, как угодно средствами Windows. А именно, я инициализирую объект TCanvas, связываю его с полученным контекстом и вывожу в него картинку.

Ты можешь использовать с этим контекстом все знакомые тебе функции рисования WinAPI. Только помни, эти функции не будут работать быстрее. Они так же тормозят, как и сам GDI.

После использования контекста его необходимо уничтожить FPrimarySurface.ReleaseDC (DC).

Всё. С DirectDraw покончено. На сегодня больше никаких функций. DirectDraw.

На последок только одна маленькая косметическая операция. Брось на форму компонент ApplicationEvents, чтобы можно было вылавливать сообщения приложения. Нам понадобятся два сообщения OnDeactivate (когда приложение потеряло фокус) и OnRestore (когда вернуло фокус).

Когда ты переключаешся на другое приложение (сообщение OnDeactivate) нужно написать следующий код:

 procedure TForm1.ApplicationEvents1Deactivate(Sender: TObject);
 begin
  Application.Minimize;
 end;

Этот код сворачивает твоё приложение, чтобы его не было видно. А когда ты попытаешься вернуть фокус программе (сообщение OnRestore), нужно восстановить окно на весь экран:

 procedure TForm1.ApplicationEvents1Restore(Sender: TObject);
 begin
  WindowState := wsMaximized;
 end;

Я напоминаю тебе, что запускать приложения DirectX (как и OpenGL) из среды Delphi не желательно. Это может привести к нежелательным ошибкам. А отлаживать приложения вообще запрещается. Отладка часто приводит к зависонам, когда ты попытаешься поставить точку остонова. Это проблема не Delphi, а самого принципа отладки. Как только прога дойдёт до точки останова, ей необходимо переключится в отладчик, что может вызвать сбой.

Вот теперь всё. Первое занятие с DirectDraw закончено. До следующей встречи!!!

 Исходники примера забирай здесь

Для примера нужны новые заголовочные файлы, в которых уже прописана поддержка интерфейсов DirectX7. Я эти файлы разместил в разделе "Скачать".


Design by FMk group ©
Copyright©: Horrific aka Флёнов Михаил ©