После выхода моей первой статьи о программировании 3-хмерной графики в Делфи «OpenGL и Delphi» (MK №19 (190)), мне начали приходить письма с различными вопросами по этой теме и с пожеланиями продолжить рассмотрение основ программирования с использованием OpenGL. Но, как уже отмечалось, это материал не одной статьи. Поэтому в этом цикле будут рассмотрены лишь некоторые аспекты программирования, по которым (судя по письмам) у начинающих программистов OpenGL чаще всего возникают вопросы. И для начала — материал о том, как оптимизировать приложение, использующее OpenGL, и сделать его более эффектным.
Вид
Многие программы — например, игры — работают на полный экран и зачастую устанавливают «свое» разрешение. Это делает выводимую графику более эффектной и позволяет сосредоточить на ней внимание пользователя. Во многих случаях полноэкранные приложения работают быстрее, чем в окне. Предлагаю вам один из способов создания такого приложения. Он заключается в следующем: создается форма (на нее ваше приложение будет выводить графику), у которой BorderStyle=bsNone, FormStyle=StayOnTop, а ClientHeight и ClientWidth соответствуют значениям будущего разрешения экрана по вертикали и горизонтали (например, 480 и 640, 600 и 800 и т.д.) Таким образом, при установке требуемого разрешения форма будет занимать весь экран и находиться поверх всех окон. Собственно для установки разрешения используется функция WinAPI ChangeDisplaySettings. Перед ее вызовом свойства формы Left и Top обнуляются — чтобы она расположилась в левом верхнем углу экрана. Пример (обработчик события OnCreate формы):
procedure TForm1.FormCreate(Sender: TObject); var td:tdevmode; begin form1.Left:=0;form1.Top:=0; {положение формы} with td do begin dmsize:=sizeof(td); dmbitsperpel:=16; {разрядность цвета} dmpelswidth:=640; {разрешение по горизонтали} dmpelsheight:=480; {разрешение по вертикали} dmfields:=dm_bitsperpel or dm_pelswidth or dm_pelsheight; changedisplaysettings(td,cds_fullscreen); end; end;
После завершения работы приложения желательно вернуть прежнее разрешение. Делается это следующим образом.
{обработчик события OnDestroy формы} procedure TForm1.FormDestroy(Sender: TObject); var td:tdevmode absolute 0; begin changedisplaysettings(td,cds_fullscreen); end;
Такой способ довольно удобен и без глюков работает на разных конфигурациях. Единственное «но» — устанавливать можно только стандартные разрешения.
Размер
Следующей немаловажной деталью любого приложения является размер. Одно и то же откомпилированное в разных версиях Делфи приложение имеет разный размер. Так, размер созданного в 3-й версии exe-файла (если просто после запуска Делфи сделать компиляцию пустой формы) — около 200 Кб, в то время как 6-я выдаст порядка 400. Это своеобразная плата за удобство и простоту — в новых версиях в подключаемые модули добавляются новые полезные процедуры, которые зачастую в приложении, работающем с OpenGL, не используются, а только лишь увеличивают объем exe-файла. Чтобы избежать этого, следует отказаться от использования VCL (Visual Component Library), т.е. всего того программного комплекса, который дает возможность создавать и использовать компоненты, определять их свойства и методы с помощью Инспектора Объектов, а также определять события и реакции на них и т.п. После долгого использования VCL переход на программирование с помощью функций API может показаться довольно сложным. Надеюсь, приведенный далее простой пример, взятый мною из примеров к книге М. Краснова «OpenGL и Делфи» поможет вам освоиться.
Впрочем, если вас не смущают лишние 250 Кб, то и не стоит лишний раз беспокоиться. Пример (для сомневающихся объясняю: создайте console application, уберите оттуда все, вставьте туда это):
uses Windows,Messages; const AppName = 'Without VCL'; {название приложения — будет выведено в списке задач по Alt+Ctrl+Del} Var Window : HWnd; {ссылка на окно} Message : TMsg; WindowClass : TWndClass; {оконная функция} function WindowProc (Window : HWnd; Message, WParam : Word; LParam : LongInt) : LongInt; stdcall; begin WindowProc := 0; {обработка сообщений} case Message of {сообщение: реакция} wm_Destroy : begin PostQuitMessage (0); {выход} Exit; end; end; WindowProc := DefWindowProc (Window, Message, WParam, LParam); end; {начало программы} begin with WindowClass do begin Style := cs_HRedraw or cs_VRedraw; {при изменении размеров окна — перерисовка по горизонтали и вертикали} lpfnWndProc := @WindowProc; cbClsExtra := 0; cbWndExtra := 0; hInstance := 0; hIcon := LoadIcon (0, idi_Application); {иконка по умолчанию} hCursor := LoadCursor (0, idc_Arrow); {обычный вид курсора} hbrBackground := GetStockObject (White_Brush); {фон окна} lpszMenuName :=''; lpszClassName := AppName; end; If RegisterClass (WindowClass) = 0 then Halt (255); {если не удалось зарегистрировать — аварийное завершение программы} Window := CreateWindow (AppName,'Without VCL',ws_OverlappedWindow, 50,50,640,480,0,0,HInstance,nil); ShowWindow (Window, CmdShow); UpdateWindow (Window); while GetMessage (Message, 0, 0, 0) do begin TranslateMessage (Message); DispatchMessage (Message); end; Halt (Message.wParam); end.
Это уже рабочая программа, которая выводит пустую форму с заданными размерами и положением на экране (откомпилированный exe-файл занимает всего лишь 17 Кб и может распространяться, в отличие от C++-Buider’овских экзешников, без каких-либо дополнительных DLL). Не буду особо углубляться в описания работы функций (поверьте, это долго и много), а лишь вкратце опишу принципы работы. Итак, программа сильно отличается по виду, хотя принципы событийно-ориентированного приложения в ней сохранены. Структура — обычная для Pascal. Для работы ей нужны всего два модуля —Windows и Messages. Состоит из оконной функции WindowProc и основного кода. В оконную функцию передаются поступающие сообщения. Если реакция на них не описана в блоке case, то их обрабатывает функция DefWindowProc. В примере код обработчика сообщения WM_DESTROY содержит строки, завершающие работу приложения. Названия сообщений схожи с названиями VCL-событий (OnCreate —WM_Create; OnPaint —WM_Paint и пр.) Основной код начинается с определения атрибутов класса окна заданием полей структуры WindowClass (подробнее о них — в хелпе Win SDK), в которых, кроме всего прочего, задается название (вернее, адрес) оконной функции. После этого функция RegisterClass регистрирует класс окна в системе, а CreateWindow создает окно (или оконный элемент) с заданием заголовка окна, стиля, положения на экране, размеров и пр. Затем окно отображается (ShowWindow) и перерисовывается (UpdateWindow). И заканчивается основной код циклом обработки сообщений. В нем-то и происходит получение сообщения и передача его в оконную функцию.
Чтобы расширить функциональность этого приложения, надо дополнить его своими обработчиками сообщений (почти так же, как и при работе с VCL). Для многих приложений, использующих OpenGL, требуется только форма, поэтому, отказавшись от VCL, можно уменьшить размер приложения почти в 10 раз. И еще: чтобы сделать окно без заголовка и границ (и поверх всех окон), в функции CreateWindow замените параметр стиля ws_overlappedwindow на комбинацию ws_visible or ws_popup or ws_ex_topmost. Чтобы не было видно курсора мыши (к примеру, в полноэкранных приложениях), допишите в программу строку ShowCursor(False). Также в некоторых случаях при переходе в полноэкранный режим с экрана не убирается Панель Задач (Taskbar) — в этом случае следует попробовать ShowWindow со вторым параметром SW_MAXIMIZE.
Скорость
Вы, наверное, замечали, что приложения, использующие стандартный таймер Делфи, могут работать с разной скоростью на разных компьютерах. Казалось бы, задан интервал, к примеру, 50 мс, выводится графика Так почему же у одного приятеля на компе с GeForce4 и Pentium 4 этот интервал резко уменьшается, а у другого (с S3 Trio 1 Мб и Pentium 90) сильно растягивается (может, это БГ тайно проводит опыты со временем? Кое-кто не зря жаловался, что загрузка Win98 кажется ему вечностью :-)). Нет, это просто особенность стандартного таймера: если компьютер не успевает выполнить реакцию на тик таймера, то она становится в т.н. очередь, и поэтому приложение может работать по-разному. Один из способов избежать этого заключается в использовании мультимедийного таймера (из модуля mmsystem). Этот таймер позволяет задавать интервал, разрешение (определяет количество миллисекунд, которое можно отвести на обработку тика, т.н. точность таймера; 0 — максимальная), а также процедуру, обрабатывающую тик. Чтобы использовать этот таймер, допишите к списку подключаемых модулей mmsystem. Для запуска таймера в программе используется такая запись:
timer:=timesetevent(50,0,@Ontimer,0,TIME_PERIODIC);
timer — переменная, типа uint, идентификатор таймера.
Интервал — 50 мс, точность — наивысшая, обработчик — процедура Ontimer. Процедура является пользовательской и не должна иметь какого-либо отношения к классу формы:
procedure Ontimer(uTimerID, uMessage: UINT;dwUser, dw1, dw2: DWORD) stdcall; begin {текст обработчика} end;
Итак, операция будет выполняться на каждый тик таймера. При завершении работы приложения следует «убить» таймер (иначе ваша ОС начнет сильно тормозить): TimeKillEvent (timer). Таким образом, мультимедийный таймер позволяет создавать приложения, работающие с одинаковой скоростью на разных компьютерах. На маломощных машинах это достигается путем пропуска нескольких кадров анимации. Например, на моей отнюдь не самой мощной конфигурации в особо тяжелых случаях успевают выводится первый и последний кадр анимации, зато я знаю, что на более мощных компьютерах эта анимация будет выводиться плавнее (т.е. как надо, со всеми кадрами), но за то же время. И поэтому без труда можно устроить «грубую» привязку к музыке и прочие увеселения. Конечно же, это не решение проблемы, но все же дает программисту возможность «прыгнуть» немножко выше возможностей своего компьютера :-).
Количество кадров
Иногда требуется узнать, сколько кадров выводит приложение за секунду, т.н. FPS (Frames Per Second). Вот один из способ, позволяющих это сделать:
… Var NewCount, FrameCount, LastCount: LongInt; {счетчики кадров} FpsRate: GLFloat; {FPS} … newCount := GetTickCount; {получаем кол-во мс, прошедших с начала работы ОС} Inc(frameCount); {увеличение счетчика кадров после вывода кадра} If (newCount — lastCount) > 1000 then begin {прошла секунда} fpsRate := frameCount * 1000 / (newCount — lastCount); lastCount := newCount; frameCount := 0; end; …
Количество выведенных за секунду кадров — в переменной fpsRate.
На сегодня это все. Теперь вы сможете улучшить свои программы, сделать их более профессиональными с помощью предложенного программного арсенала средств. В следующей статье уже будет информация непосредственно по OpenGL.
P.S. Что касается материалов по OpenGL в Интернете, рекомендую проверить следующие ссылки:
http://www.opengl.org — здесь если и не все, то по крайней мере многое. FAQ’s, хэлпы, новости, документация и много-много другой интересной и полезной информации.
http://www.delphi.vitpc.com — сайт «Королевство Делфи». Имеется рубрика М. Краснова по использованию OpenGL и Делфи, а также информация чисто по Делфи.
Также на сайте http://www.torry.ruесть много компонентов и модулей для работы с OpenGL.
Остальные сайты, которые мне встречались, содержат мало информации по данной тематике и не заслуживают особого внимания. Кроме того, советую активно использовать хэлп в Делфи — в нем есть ответы практически на все вопросы (просто надо хорошо поискать).
