Вміст контексту

Отже, якщо необхідно здійснити виведення на поверхню чужого вікна, треба,
отримавши посилання на це вікно, отримати посилання на контекст пристрою, пов'язану з
цим вікном, після чого можна малювати на чужому вікні. Коли ми працюємо з OpenGL,
ми створюємо контекст відтворення OpenGL, пов'язаний з контекстом пристрою
нашого вікна, далі звертаємося до функцій OpenGL, відпрацьовуючи які, OpenGL
будує картинку на поверхні нашого пристрою.

Тобто схема тут така – застосування віддає команди машині OpenGL, отримуючи
результат на поверхні свого вікна. Така архітектура називається "клієнт –
серверною ", в ролі клієнта виступає наш додаток, в ролі сервера – система
OpenGL. Завдання сервера – відпрацьовувати команди клієнта. У принципі, сервер може
розташовуватися і не на нашому комп'ютері, а віддалено, можливо і за тисячі
кілометрів від нас. Наше додаток повинен передати сервера необхідні для роботи
дані – контексти і команди на мові сервера. Також важливо розуміти, що
основну роботу виконує не наш додаток, а сервер. Наше застосування лише
створює вікно – платформу для роботи сервера, і передає команди сервера. При
грамотному підході немає різниці, якими засобами ми спроектували додаток –
С або Delphi, швидкість відтворення цілком залежить від продуктивності
сервера – машини OpenGL. Ми вибрали в якості засобу розробки додатків
саме Delphi за видатну швидкість компіляції і привабливість концепцій.

Оскільки контексти займають важливе місце в нашій роботі, поговоримо про
них трохи більш докладно.

Ми знаємо, що посилання на контекст пристрою – величина типу HDC, для
одержання якої викликаємо функцію GetDC. Посиланням на контекст пристрою в Delphi
відповідає властивість Canvas.Handle форми, принтера і деяких компонентів.
Теоретично усюди в наших прикладах у рядках, що використовують величину DC типу
HDC, замість DC можна використовувати Canvas.Handle. У перших прикладах для
початківців це так і зроблено. Який же все-таки сенс контексту пристрою, якщо
він і так пов'язаний з однозначно певним об'єктом – вікном, областю пам'яті або
принтером, і навіщо передавати додатково якусь інформацію про однозначно
певному об'єкті?

Для відповіді на ці питання розберемося з виведенням у Windows, чудовим
властивістю якого є те, що одними і тими ж функціями здійснюється
висновок на різні пристрої.

Рядки

Form1.Canvas.Ellipse (0, 0, 100, 100);

і

Printer.BeginDoc;
Printer.Canvas.Ellipse (0,0,100,100);
Printer.EndDoc;

малюють один і той же еліпс на поверхні форми і на надрукованих
документі відповідно, тобто на різних пристроях. Причому, якщо ми
будемо виводити різнобарвну картинку на монохромний принтер, він впорається з цим
завданням, передаючи кольору відтінками сірого. Навіть якщо ми малюємо тільки на канві
форми, ми маємо справу з різними пристроями – нам невідомо, якою є
графічна плата користувача і які характеристики поточної інсталяції
налаштувань екрана. Наприклад, маючи у своєму розпорядженні більше 16 мільйонів квітів,
додаток не піклується про відображення такої багатої палітри на екрані,
располагающем всього 256 квітами. Ці питання додаток перекладає на плечі
операційної системи, що вирішує їх за допомогою драйверів пристроїв. Додатком
для того, щоб скористатися функціями відтворення Windows, необхідно
тільки вказати посилання на контекст пристрою, що містить засоби і
характеристики пристрою виводу.

Win32 Programmer "s Reference фірми MicroSoft про контекст пристрою повідомляє
наступне:

"Контекст пристрою є структурою, яка визначає комплект
графічних об'єктів та пов'язаних з ними атрибутів, і графічні режими,
впливають на висновок. Графічний об'єкт включає олівець для зображення лінії,
щітку для фарби та заповнення, растр для копіювання або прокручування частин
екрану, палітру для визначення комплекту доступних кольорів, області для
відсікання та інших операцій, і маршруту для операцій малювання ".

Термін "структура", зустрівся тут, відповідає запису в термінології
Delphi. Контекст пристрою Windows містить інформацію, що відноситься до
графічним компонентів GDI, контекст відтворення містить інформацію,
відноситься до OpenGL, тобто грає таку ж роль, що і контекст пристрою
для GDI. Зокрема, ці контексти є сховищами стану системи,
наприклад, зберігають інформацію про поточний кольорі олівця.

Формат пікселя


Отже, посилання на контекст пристрою містить характеристики пристрою і
засоби відображення. Саме він знає, як виводити на конкретно цей пристрій.
Спрощено кажучи, отримавши посилання на контекст пристрою, ми беремо в руки простий
або кольоровий олівець, або пензлик з палітрою в мільйони відтінків. Сервер
OpenGL, перш ніж приступати до роботи, також повинен визначитися, на якому
обладнанні йому доведеться працювати. Це може бути скромна персоналка, а може
бути і потужна графічна станція. Перш ніж отримати контекст відтворення,
сервер OpenGL повинен отримати детальні характеристики використовуваного
обладнання. Ці характеристики зберігаються в спеціальній структурі, тип якої –
TPixelFormatDescriptor (опис формату пікселя). Формат пікселя визначає
конфігурацію буфера кольору і допоміжних буферів.

Найчастіше питання, який я отримую в зв'язку з моїми уроками, полягає в
прохання вказати джерела інформації про OpenGL російською. До
жаль, якщо такі і є, то мені вони невідомі. Головним нашим підручним
стане поставляється в складі Delphi файл допомоги по OpenGL. Систему допомоги
Delphi для отримання гарних результатів необхідно налаштовувати, якщо у допомозі
Delphi знайти розділ з OpenGL, він не порадує великою кількістю інформації. У різних
версіях Delphi настройка допомоги виконується по-різному, потрібно деякі
нескладні маніпуляції, але ми не будемо витрачати на це час. Будемо використовувати
найпростіший спосіб – контекстну допомогу. Наберіть в тексті модуля фразу
"PixelFormatDescriptor", натисніть клавішу F1 і Ви отримаєте детальну допомогу про
цьому типі. Так само ми будемо отримувати допомогу про всі термінах, функції та
командах OpenGL.

Отже, ми отримали велике опис структури PixelFormatDescriptor. Звертаю
увагу, що ми бачимо розділ допомоги MicroSoft, розрахованої на програмістів З
і С + +, тому опис використовує терміни і стилістику саме цих мов. Так,
за традицією Delphi імена типів починаються з префікса T, але нам не вдасться знайти
допомога по терміну TPixelFormatDescriptor. На жаль, це не єдине
незручність, що нам доведеться випробовувати. Наприклад, якщо зараз ми заглянемо в
файл windows.pas і знайдемо опис запису TPixelFormatDescriptor, ми виявимо,
що у файлі допомоги не вказані деякі константи, а саме:
PFD_SWAP_LAYER_BUFFERS, PFD_GENERIC_ACCELERATED і PFD_DEPTH_DONTCARE. А
константа, названа PFD_DOUBLE_BUFFER_DONTCARE, мабуть, відповідає
константі, описаної в модулі windows.pas як PFD_DOUBLEBUFFER_DONTCARE.
Напевно, більш пізні версії допомоги та заголовного файлу виправлять цей та
інші неточності.

Отже, сенс структури PixelFormatDescriptor – детальний опис графічної
системи, на якій відбувається робота. Вас може спантеличити дійшлість цього
опису, але, запевняю, особливу увагу від нас з усього цього опису потребує
зовсім небагато.

У каталозі Beginner / 1 Ви знайдете проект OpenGL_min.dpr, в якому я навів
опис всіх полів структури TPixelFormatDescriptor російською, в момент їх
первісного заповнення. Робиться це в процедурі SetDCPixelFormat,
викликається між отриманням посилання на контекст пристрою і створенням контексту
відтворення OpenGL. Подивимося докладніше, що там робиться. Полях структури
присвоюються бажані значення, потім викликом функції ChoosePixelFormat
здійснюється запит системі, чи підтримується на даному робочому місці
вибраний формат пікселя, і викликом функції SetPixelFormat встановлюємо формат
пікселя в контексті пристрою. Функція ChoosePixelFormat повертає індекс
формату пікселя, який нам потрібен як аргумент функції SetPixelFormat.

Заповнивши поля структури TPixelFormatDescriptor, ми визначаємося зі своїми
побажаннями до графічній системі, на якій буде відбуватися робота
додатки, машина OpenGL вибирає до наших побажань
формат, і встановлює вже його в якості формату пікселя для подальшої
роботи. Наші побажання коригуються стосовно реальним характеристикам
системи. Те, що машина OpenGL не дозволить нам встановити нереальний для
конкретної машини формат пікселя, значно полегшує нашу роботу.
Припускаючи, що розроблене додаток буде працювати на машинах різного
класу, можна запросити "всього побільше", а вже OpenGL розбереться на конкретній
машині, які параметри і можливості обладнання, на якому зараз буде
виконуватись робота. На цьому можна було б і закінчити розмову про формат
пікселя, якщо б ми могли повністю довіритися вибору OpenGL.

Звернемо увагу на поле структури "бітові прапори" – dwFlags. Те, як ми
задамо значення прапорів, суттєво може позначитися на роботі нашого
додатки, і навмання задавати ці значення не варто. Тим більше, що деякі
прапори спільно ужитися не можуть, а деякі можуть бути присутніми тільки в парі
з іншими. У цьому прикладі прапорів я присвоїв значення PFD_DRAW_TO_WINDOW or
PFD_SUPPORT_OPENGL, тобто повідомляю системі, що я збираюся здійснювати вивід
у вікно, і що моя система в принципі підтримує OpenGL. Я обмежився всього
двома константами з великого списку, наведеного в модулі windows.pas, по
кожної з яких у файлі допомоги наведено детальний опис.

Так, константа PFD_DOUBLEBUFFER включає режим подвійної буферизації, коли
вивід здійснюється не на екран, а на згадку, потім вміст буфера виводиться
на екран. Це дуже корисний режим, якщо в будь-якому прикладі на анімацію прибрати
режим подвійної буферизації і всі команди, пов'язані з цим режимом, добре буде
видно мерехтіння при виведенні кадру. Константу PFD_GENERIC_ACCELERATED має сенс
встановлювати у разі, якщо комп'ютер оснащений графічним акселератором. Прапори,
закінчуються на "DONTCARE", повідомляють системі, що відповідний режим
може мати обидва значення, тобто PFD_DOUBLE_BUFFER_DONTCARE – запитуваний
формат пікселя може мати обидва режими – одинарної і подвійної буферизації. З
всіма іншими полями і константами я надаю Вам можливість розібратися
самостійно, тільки зауважу, що поле iLayerType, описане в windows.pas типу
Byte, може, відповідно до допомоги, мати три значення: PFD_MAIN_PLANE,
PFD_OVERLAY_PLANE і PFD_UNDERLAY_PLANE, однак константа PFD_UNDERLAY_PLANE
має значення -1, так що встановити таке значення не вдасться.

OpenGL дозволяє дізнатися, який же формат пікселя він збирається використовувати.
Для цього необхідно використовувати функцію DescribePixelFormat, що заповнює
величину типу TPixelFormatDescriptor встановленим форматом пікселя. На основі
використання цієї функції побудуємо просту аплікацію, що дозволяє детальніше
розібратися з форматом пікселя і підібрати формат для конкретного робочого
місця.

У прикладі двійкового прапорів задаємо всі можливі значення одночасно, числовим
полях задаємо свідомо нереальне значення 64, і дивимося на вибір формату
пікселя, зробленим OpenGL. Результат, який Ви отримаєте – вибраний формат
пікселя, я передбачити не зможу – він індивідуальний для кожної конкретної
конфігурації машини і поточних налаштувань. Можливо, Ви отримаєте в результаті, що
режим подвійної буферизації не буде встановлено – нагадую, багато прапори
встановлюються тільки в комбінації з іншими визначеними. Наше застосування
дозволяє змінювати параметри формату пікселя і встановлювати його заново. Щоб
бачити, що відбувається відтворення, невеличкий майданчик на екрані при кожному
тестуванні забарвлюється випадковим кольором, використовуючи функції OpenGL.
Поекспериментуйте з цим додатком, наприклад, визначте комбінацію прапорів
для встановлення режиму подвійної буферизації. Подивіться значення числових полів
формату при різній палітрі екрана – 16, 24, 32 біта, але не 256 кольорів. Про
виведення при палітрі екрану в 256 квітів – окрема розмова. Цей додаток, у
Зокрема, дає відповідь на питання – як визначити, чи оснащений комп'ютер
графічним акселератором. Повозившись з цим додатком, Ви знайдете відповідь на
питання, на який я Вам відповісти не зможу – як треба заповнити структуру
TPixelFormatDescriptor для Вашого комп'ютера. Зверніть увагу, що в коді я
встановив кілька перевірок на відсутність контексту відтворення, який
може бути втрачений по ходу роботи будь-якого додатка, що використовує OpenGL –
рідкісна, але можлива ситуація в штатному режимі роботи системи і дуже ймовірна
ситуація якщо, наприклад, по ходу роботи програми міняти налаштування екрану.

Мінімальна програма OpenGL


Тепер ми знаємо все, що необхідно для побудови мінімальної програми,
використовує OpenGL. Я навів два варіанти цієї програми – одна побудована
виключно на функціях Windows API, інша використовує бібліотеку класів
Delphi (проекти каталогів Beginner / 1 і Beginner / 2 відповідно).

Погляньмо на головний модуль другого проекту. При створенні форми задаємо формат
пікселя, в якості посилання на контекст пристрою використовуємо значення
Canvas.Handle форми. Створюємо контекст відтворення OpenGL і зберігаємо в
змінної типу HGLRC. При обробці події OnPaint встановлюємо контекст
відтворення, викликаємо функції OpenGL і звільняємо контекст. При завершенні
роботи програми видаляємо контекст відтворення. Для повної академічності
можна включити рядки, перевіряючі, отриманий чи контекст відтворення, і не
втрачається чи він по ходу роботи. Ознакою таких ситуацій є нульовий
значення змінної hrc. У мінімальній програмі я просто офарблюю вікно в
жовтуватий відтінок. Отримавши допомогу по команді glClearColor, Ви можете дізнатися,
що аргументи її – трійка дійсних чисел в інтервалі [0; 1], які задають частку
червоного, зеленого і синього складових у кольорі і ще один, четвертий аргумент,
про яке ми поговоримо трохи пізніше. Цьому аргументу я в прикладі задав значення
1.0. Взагалі то, аргументи glClearColor, згідно допомоги, мають невідомий тип
GLclampf. Для того, щоб розібратися з цим типом, відсилаю до рядка

GLclampf = Single;

модуля opengl.pas.

Докладну розмову про типи OpenGL теж доведеться поки відкласти, щоб не
завантажити надмірним великою кількістю інформації.

Рядок нашої програми

glClear (GL_COLOR_BUFFER_BIT);

будемо розуміти як очищення екрана, фон при цьому покривається заданим кольором.

Проект, побудований тільки на функціях API, сподіваюся, зараз став більш
зрозумілим. Замість Canvas.Handle використовуємо власну змінну dc, в
обробнику події WM_PAINT реалізуємо дії, які Delphi при звичайному
підході виконує за нас. Нагадую, що для кращої стійкості роботи
обробник WM_PAINT слід було б написати так:

dc := BeginPaint (Window, MyPaint);
wglMakeCurrent (dc, hrc);
glClearColor (0.85, 0.75, 0.5, 1.0);
glClear (GL_COLOR_BUFFER_BIT);
wglMakeCurrent (dc, 0);
EndPaint (Window, MyPaint);
ReleaseDC (Window, dc);

А в обробнику WM_DESTROY слід перед PostQuitMessage додати рядок:

DeleteDC (dc);

Тобто всі використовувані посилання необхідно звільняти, а після того, як вони
стали не потрібні – видаляти.

Я раджу Вам зараз поекспериментувати з цією програмою, заповніть поля
формату пікселя знайденими значеннями, відповідними Вашій системі,
спробуйте видалити рядки, відповідні отримання контексту відтворення,
поміняйте відтінок використовуваного фону і тільки після того, як кожен рядок коду
буде повністю зрозуміла, переходьте до подальшого матеріалу.

У всіх своїх прикладах я приписав рекомендацію не запускати проекти,
використовують OpenGL під управлінням середовища Delphi. Справа в тому, що часто в таких
ситуаціях програма аварійно переривається, видаючи повідомлення "access violation
– ". Це відбувається і у випадку самої акуратної роботи з контекстами, і не
пов'язано з недбалістю роботи програми. Деякі програмісти провину за це
покладають на софтверні драйвери і рекомендують відновити їх. Деякі
стверджують, що справа в Windows 9X, і під NT цього не відбувається. Можливо, Ви
теж нічого такого не помічали і не можете зрозуміти, про що я зараз веду
мова. У мене такі віконця вилітають через раз на одному і тому ж проекті, хоча
відкомпільований модуль працює чудово. Я вважаю, що якщо драйвери не
"Глюк", коли програма працює без середовища Delphi, справа не тільки в
драйверах.

Висновок на поверхню компонентів


Теоретично функціями OpenGL можливо здійснювати вивід не тільки на
поверхню форми, а і на поверхню будь-якого компонента, якщо у нього є
властивість Canvas.Handle, для чого при отриманні контексту відтворення
необхідно вказувати саме його посилання на контекст пристрою, наприклад,
Image1.Canvas.Handle. Однак найчастіше це призводить до нестійкої роботи,
висновок "тобто, то ні", хоча контекст відтворення присутній і не
втрачається. Я раджу Вам завжди користуватися висновком виключно на поверхню
вікна. OpenGL чудово уживається з візуальними компонентами, як видно з
прикладу TestPFD, якщо ж необхідно обмежити розмір області виведення, для цього
є стандартні методи, про які ми обов'язково будемо розмовляти в майбутньому.

Просто заради інтересу наведу приклад, коли висновок OpenGL здійснюється на
поверхню панелі, тобто компонента, що не має властивості Canvas. Для цього
ми користуємося тим, що панель має окреме вікно, викликаємо функцію GetDC з
аргументом Panel1.Handle.

Точно також Ви можете виводити на поверхню будь-якого компонента, що має
властивість Handle, наприклад, на поверхню звичайної кнопки. Обов'язково спробуйте
зробити це.

Для виведення на компонент класу TImage можете записати:

dc := Image1.Canvas.Handle;

і видалити рядки BeginPaint і EndPaint, оскільки TImage не має властивості
Handle, тобто не створює окремого вікна. Проте висновок на такі компоненти як
разів відрізняється повною нестійкістю, так що я не гарантую Вам надійного
позитивного результату.

Взагалі, замість використання Canvas.Handle краще використовувати самостійно
отриману аналогічну величину, замість обробки подій краще використовувати
свої пастки повідомлень. Такий підхід призводить до максимальної швидкості і
надійності роботи програми.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*