Это вторая статья из четырёх. Полный цикл выглядит так:
Привет, 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.
