Вміст контексту, Робота з графікою, Delphi, статті

Отже, якщо необхідно здійснити виведення на поверхню чужого вікна, треба, отримавши посилання на це вікно, отримати посилання на контекст пристрою, пов’язану з цим вікном, після чого можна малювати на чужому вікні. Коли ми працюємо з 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>

*

*