SerafimArts1 час назад
OpenGL в PHP не привлекая внимания санитаров: Привет, OpenGL
Уровень сложностиСреднийВремя на прочтение6 минОхват и читатели2.2KPHP*Ненормальное программирование*ТуториалЭто вторая статья из четырёх. Полный цикл выглядит так:
• Привет, окно
• Привет, OpenGL
• OpenGL расширения
• Рисуем треугольникСсылки на следующие части добавлю позже — по мере появления материалаИсходный КодВсе исходники второй главы можно потыкать на GitHub.В предыдущей статье мы открыли окно. Теперь давайте его не просто игнорировать.
Структура проекта
Если вы заглядывали в исходники из первой части, то могли заметить одну небольшую, но важную деталь:
Я вынес GLFW в отдельный файл src/glfw3.php и добавил туда поддержку разных ОС, хоть и не писал об этом в самой статье.
Получилось примерно так:
<?php
return FFI::cdef(
code: (string) file_get_contents(__FILE__, offset: __COMPILER_HALT_OFFSET__),
lib: $_SERVER['GLFW3_LIB'] ?? match (PHP_OS_FAMILY) {
'Windows' => 'glfw3.dll',
'Linux' => 'libglfw.so.3',
'Darwin' => 'glfw3.dylib',
},
);
__halt_compiler();
// ...заголовки...А структура проекта стала такой:
app/
├── src/
├ └── glfw3.php
├── app.php
└── glfw3.dll # в .gitignoreТ.е. я создал "мусорку" в src, куда будет скидываться всякий "shared" код.
Поддержка разных ОС
Сразу честно: Такая поддержка, что я там впихнул — довольно маргинальная.
Так как в коде указаны относительные пути, то поведение самого код зависит от:
• CWD (текущей рабочей директории)
• PATH (параметра окружения с путями ко всяким бинарям и проч)
• возможно ещё от всяких LD_LIBRARY_PATH (параметра окружения для линкера С), но это не точно (не проверял)
• фазы луны (иногда кажется, что и от неё)Более того, в TS и ZTS сборках PHP поведение параметров окружения и рабочей директории отличается. Сборка PHP TS вместо работы непосредственно с данными — реализует "костыль", виртуальную хешмапу, которая вообще никак не связана с реальными переменными ОС. Но в нашем случае проблем никаких из-за этого не должно возникнуть.В любом случае, в мире PHP такое обращение с кодом считается незаконным, потому что он начинает зависеть от внешних факторов и ведёт себя как хочет. Гораздо надёжнее использовать явные пути:
return FFI::cdef(..., lib: __DIR__ . '/path/to/lib.<dll/so/dylib/etc>');Но так как у нас и так тут хватает извращений с кодом, то "и так сойдёт".
При желании, вы всегда можете указать явный путь к библиотеке в переменной окружения GLFW3_LIB как явно:
$ GLFW3_LIB=/path/to/lib.so php ./app.phpТак и через PHP код:
// app.php
$_SERVER['GLFW3_LIB'] = '/path/to/your/glfw3.dll';
$glfw = require __DIR__ . '/src/glfw3.php';Будем считать что такой вариант решения всяких проблем делает написанный код очень гибким и масштабируемым.
Наконец-то, OpenGL
В любом случае, сейчас предстоит сделать тоже самое, что было проделано для GLFW, но уже для OpenGL!
Windows-режим "повезло"
Говоря о Windows — тут всё просто:
• Используем opengl32.dll
• Ничего устанавливать не нужно
• Базовая версия (1.0 и даже куски 1.1) есть "из коробки"
• Остальное подтянется через драйвер видеокарты (вплоть до 4.6)Как именно "подтянется", что за загрузчики, причём тут сам GLFW и прочие WGL/GLX - будет в следующей статье.Минимальный пример:
return FFI::cdef(
code: (string) file_get_contents(__FILE__, offset: __COMPILER_HALT_OFFSET__),
lib: $_SERVER['OPENGL_LIB'] ?? match (PHP_OS_FAMILY) {
'Windows' => 'opengl32.dll',
// TODO
},
);
__halt_compiler();
// TODOВажно (!!!) Сейчас мы подключаем OpenGL отдельно от GLFW — как будто это два независимых мира и если у вас такое не работает (я про Linux, macOS и прочие, которые я просто проигнорировал) — не переживайте. Позже мы всё равно перейдём на использование OpenGL через GLFW и починим работоспособность на других ОС.
Подключаем
Всё просто: Берём свежесозданный src/opengl.php в правую руку и app.php в левую. Скрещиваем и получаем:
<?php // app.php
$glfw = require __DIR__ . '/src/glfw3.php';
$opengl = require __DIR__ . '/src/opengl.php';
// ...а дальше инициализация и запуск окна, как раньшеУра, мы подключили OpenGL и можем начать его интегрировать внутрь программы!
Интегрируем
Чтобы OpenGL начал работать с нашим окном, нужно:
• Добавить константы GLFW и OpenGL
• Добавить функции оных
• Вызвать всё это в нужном порядкеЗвучит просто. Как и всё в программировании, до первого запуска.
Конфигурация OpenGL
Во-первых, начнём с добавления некоторых правил нашего контекста в которых я не буду сильно уходить от классических рекомендаций.
Для GLFW, так как он предоставляет глубокую интеграцию графического API (не только OpenGL), достаточно прописать несколько правил конфигурации сразу после инициализации:
// app.php // ...наша инициализация была тут, т.е. // if (!$glfw->glfwInit()) { ... }
// Включаем мультисемплинг (сглаживание). Значение 4 означает 4х-кратное MSAA
// сглаживание, чтобы наши будущие шедевры выглядели чуть менее "зубастыми".
//
// Конечно, это всё можно сделать через OpenGL, а не через GLFW,
// но почему бы и нет?
$glfw->glfwWindowHint(GLFW_SAMPLES, 4);
// Тут мы выставляем версию OpenGL, которую мы хотим использовать.
// Версия 3.3 и довольно современная, и довольно хорошо распространённая версия.
// При желании вы можете выставить и самую максмальную.
//
// Если указанная версия не поддерживается видеокартой,
// то при создании окна вы получите ошибку.
//
// Предлагаю выставить версию 42 и убедиться в этом самостоятельно ;)
$glfw->glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
$glfw->glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// А вот эта опция включает режим "прямой совместимости".
// Для OpenGL 3+ означает, что мы НЕ будем использовать старые функции OpenGL.
$glfw->glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, 1);
// Выбираем профиль OpenGL.
// Значение GLFW_OPENGL_CORE_PROFILE говорит о том, что мы хотим использовать
// исключительно современные возможности OpenGL, без обратной совместимости
// со старым API.
//
// Нахрена 100500 разных опций для того, чтобы убедить рантайм в том,
// что мы не будем заниматься некромантией — покрыто мраком.
$glfw->glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// ...а тут мы создавали окно, т.е.
// $window = $glfw->glfwCreateWindow(640, 480, 'Hello World', null, null);
Откуда взялись эти константы и функции?
Ого, вы внимательны, если заметили, что я начал использовать какие-то новые константы, да и функции тоже.
Конечно же они не появились из воздуха. Их требуется определить в src/glfw3.php:
<?php // src/glfw3.php
// Константы, которые мы использовали можно добавить сверху
//
// Значения для них взяты прямо из исходников GLFW, например:
// https://github.com/glfw/glfw/blob/3.4/include/GLFW/glfw3.h#L1008
const GLFW_SAMPLES = 0x0002100D;
const GLFW_CONTEXT_VERSION_MAJOR = 0x00022002;
const GLFW_CONTEXT_VERSION_MINOR = 0x00022003;
const GLFW_OPENGL_FORWARD_COMPAT = 0x00022006;
const GLFW_OPENGL_PROFILE = 0x00022008;
const GLFW_OPENGL_CORE_PROFILE = 0x00032001;
// return FFI::cdef(...);
__halt_compiler();
// ...
// Новая функция "glfwWindowHint()" так же скопипащена из заголовков:
// https://github.com/glfw/glfw/blob/3.4/include/GLFW/glfw3.h#L3053
void glfwWindowHint(int hint, int value);Подсветка PHP-кода на Хабре, как вы могли заметить, великолепно справляется с таким!Если вы всё сделали правильно, то результат так же не изменится.
Главное что никаких ошибок не возникает! В мире низкоуровневой графики отсутствие ошибок — уже успех.
Привязываем OpenGL к окну
Теперь нам нужно проделать ещё 2 операции.
Во-первых, нужно сказать OpenGL в каком окне он вообще должен работать с помощью метода/функции glfwMakeContextCurrent():
// app.php
// ...сразу после создания окна, т.е.
// $window = $glfw->glfwCreateWindow(640, 480, 'Hello World', null, null);
// Сообщаем о том, что вот этот `$window` теперь текущий контекст
$glfw->glfwMakeContextCurrent($window);
// ...а тут у нас идёт уже обработка оного, т.е.
// while (!$glfw->glfwWindowShouldClose($window)) { ... }И, соответсвенно, объявляем оную функцию в src/glfw3.php:
// src/glfw3.php
__halt_compiler();
// ...
void glfwMakeContextCurrent(GLFWwindow* window);Теперь OpenGL знает: "Ага, вот сюда рисовать".
Проверяем!
Давайте проверим?
Например, функцию очистки, которую мы сейчас добавим:
// app.php
// ...там, где мы начинаем обрабатывать события окна
while (!$glfw->glfwWindowShouldClose($window)) {
// Очищает цветовой буфер (то есть всё, что было нарисовано в предыдущем
// кадре) с использованием текущего цвета очистки (чёрного, т.к. мы его
// не задали)
$opengl->glClear(GL_COLOR_BUFFER_BIT);
// $glfw->glfwSwapBuffers($window);
// etc...
}И, соответсвенно, её декларация в src/opengl.php:
<?php // src/opengl.php
// Плюс константа const GL_COLOR_BUFFER_BIT = 0x00004000;
// ...этот ретурн не трогаем return FFI::cdef(...);
__halt_compiler();
// Плюс функция OpenGL (и тайпдеф) typedef unsigned int GLbitfield; void glClear(GLbitfield mask);
Итоги
Вы не поверите, но хоть результат и не поменялся, но...
• У нас есть окно (оно и было раньше)
• У нас есть OpenGL
• У нас есть связь между ними (присобачили одно к другому)
• Мы уже можем рисовать! Просто пока не рисуем...Дальше начинается немного жести. Будем кочевряжиться с буферами, шейдерами и чуть-чуть страдать.
По традиции, полные исходники второй части, доступны на GitHub.Теги:• php
• opengl
• glfw
• никтоведьнечитаеттегиХабы:• PHP
• Ненормальное программирование
Получайте больше инсайтов о систематизации бизнеса
Подписывайтесь на Telegram-канал Business Operations — ежедневные материалы о бизнес-процессах, операционном управлении и повышении эффективности
💬 Подписаться на канал→ Оригинальная статья