Портирование приложений OpenGL* на Android* на Intel® Atom (часть 1)

Возможность портирования игр и других приложений, интенсивно использующих 3D-графику на основе стандартов OpenGL, на устройства Android на базе Intel® Atom, имеет для нас огромную важность, поскольку существует множество уже изданных приложений, игровых движков и других программ на базе OpenGL; OpenGL обеспечивает переносимость программ, а Android постоянно расширяет поддержку OpenGL ES и C/C++. Многие приложения на базе OpenGL даже доступны в виде открытого исходного кода, например серия Quake компании Id Software. В данной статье, состоящей из двух частей, показано, как начать портирование, и подробно описаны препятствия, существующие при портировании компонентов отрисовки в приложениях, собранных в более ранних версиях OpenGL, на платформу Android на базе процессоров Intel Atom. Кроме того, здесь рассматривается портирование кода OpenGL из настольных операционных систем, таких как Windows* и Linux*, и из приложений, использующих внедренные версии OpenGL ES, с системой управления окнами или без нее.

В первой части данной статьи содержится вводная информация об использовании OpenGL ES на платформе Android с помощью пакета средств разработки программного обеспечения (SDK) или пакета Android NDK (Native Development Kit) и о том, по какому принципу выбирается тот или иной пакет. Здесь описаны различные примеры приложений OpenGL ES в SDK и NDK и интерфейс JNI (Java* Native Interface), позволяющий комбинировать компоненты Java и C/C++. Также здесь обсуждается выбор версии OpenGL ES — 1.1 или 2.0.

Во второй части рассматриваются препятствия, существующие при портировании игр OpenGL, которые необходимо изучить, прежде чем начинать подобный проект, в том числе различия расширений OpenGL, поддержка операций с плавающей запятой, сжатые форматы текстур и библиотека GLU. Кроме того, здесь описана процедура настройки системы разработки для Android на базе процессоров Intel Atom и пути достижения максимально возможной производительности при эмуляции виртуального устройства Android при использовании OpenGL ES.

Графика на платформе Android

Существуют четыре различных способа отрисовки графики на платформе Android, и у каждого из них есть свои сильные стороны и ограничения (см. табл. 1). В задачи данной статьи не входит подробное описание всех этих вариантов. С точки зрения портирования кода OpenGL с других платформ для нас важны только два из них: классы SDK Wrapper для OpenGL ES и NDK для разработки OpenGL ES в C/C++. Что касается других вариантов, SDK Canvas представляет собой мощный API-интерфейс для создания 2D-графики, который можно использовать в сочетании с OpenGL ES, однако его возможности ограничены 2D-графикой и для его использования требуется написание нового кода.

API-интерфейс Renderscript для Android изначально поддерживал OpenGL ES, но поддержка прекратилась в версии API уровня 16 (Jelly Bean) и была недоступна для новых проектов. Наилучшей областью применения Renderscript в настоящее время является повышение производительности алгоритмов с большими объемами вычислений, но низкой потребностью в выделении памяти и передаче данных, таких как расчет физических параметров в играх-симуляторах.

Таблица 1. Четыре способа отрисовки графики на платформе Android

Способ Ограничения
SDK Canvas API Только графика Java и 2D
SDK Wrapper classes for OpenGL ES Функции OpenGL ES 1.1 и 2.0 могут вызываться из Java, но с дополнительным потреблением ресурсов интерфейсом JNI
NDK OpenGL ES Функции OpenGL ES 1.1 и 2.0 с нативным кодом C/C++ могут вызываться из Java
Renderscript для OpenGL ES Поддержка OpenGL ES прекращена в версии API уровня 16

Портирование приложений OpenGL в более ранние версии Android было затруднено, поскольку старый код OpenGL был чаще всего написан на C или C++, а Android поддерживал только Java до выпуска NDK в версии Android 1.5 (Cupcake). OpenGL ES 1.0 и 1.1 поддерживались с самого начала, но производительность была неустойчива, поскольку ускорение не являлось обязательным. Однако за последние годы Android совершил огромный скачок вперед. В SDK версии Android 2.2 (Froyo) и NDK редакции 3 была добавлена поддержка OpenGL ES 2.0, а в NDK редакции 7 появилась улучшенная поддержка расширений OpenGL ES. На сегодняшний день OpenGL ES 2.0 так интенсивно используется в собственной графической платформе Android, что его наличие стало считаться обязательным. OpenGL ES 1.1 и 2.0 с ускорением теперь в обязательном порядке присутствуют на всех новых устройствах Android, что в особенности проявилось с ростом размера экранов. Сегодня Android обеспечивает устойчивую и надежную производительность приложений с интенсивным использованием 3D-графики, созданных на основе кода OpenGL ES 1.1 или 2.0 на языке Java или C/C++, и у разработчиков появилось несколько средств для упрощения процесса портирования.

Использование Android Framework SDK с классами OpenGL ES Wrapper

Платформа Android SDK предоставляет набор классов-оболочек для трех версий OpenGL ES, поддерживаемых на Android (1.0, 1.1 и 2.0). Данные классы позволяют с легкостью вызывать в системе Android драйверы OpenGL ES из кода Java, даже при том, что эти драйверы выполняются нативно. Если вы создаете новую игру OpenGL ES Android с нуля или хотите преобразовать старый код C/C++ в Java, то этот способ, вероятно, будет для вас наиболее простым. Хотя язык Java предназначен для обеспечения переносимости, при портировании приложений Java могут возникать сложности, поскольку Android не поддерживает полный набор утвержденных классов, библиотек и функций API Java Platform Standard Edition (Java SE) и Java Platform Micro Edition (Java ME). Интерфейс Android Canvas API включает в себя широкий набор функций 2D API, но он поддерживается только в Android и не обеспечивает совместимость со старым кодом.

В Android SDK есть несколько дополнительных классов, упрощающих использование OpenGL ES, например GLSurfaceView и TextureView. Класс GLSurfaceView аналогичен классу SurfaceView, который используется с Canvas API, за исключением нескольких специальных дополнительных функций для OpenGL ES. Он обрабатывает необходимую инициализацию библиотеки EGL (Embedded System Graphics Library), размещает отображаемую в Android поверхность в определенном месте на экране и создает поток. Кроме того, в него включены полезные функции для трассировки и отладки вызовов OpenGL ES. Можно быстро создать новое приложение OpenGL ES, реализовав всего три метода для интерфейса GLSurfaceView.Renderer(), как показано в табице 2.

Таблица 2. Минимально необходимые методы для интерфейса GLSurfaceView.Renderer

Метод Описание
onSurfaceCreated() Вызывается один раз, когда приложение запускается для инициализации
onSurfaceChanged() Запускается каждый раз, когда происходит изменение размера или ориентации GLSurfaceView
onDrawFrame() Вызывается повторно для отрисовки каждого кадра графической сцены

Начиная с версии Android 4.0, можно использовать класс TextureView вместо GLSurfaceView, позволяющий отрисовывать поверхности OpenGL ES с дополнительными возможностями, хотя для этого потребуется написать больше кода. Поверхности TextureView работают аналогично обычным проекциям (Views) и могут использоваться для отрисовки поверхностей за пределами экрана. Эта функция позволяет перемещать, преобразовывать, анимировать поверхности и выполнять наложение полупрозрачных изображений TextureView, когда они соединяются на экране. Класс TextureView также позволяет сочетать отрисовку OpenGL ES и Canvas API.

Классы Bitmap и GLUtils упрощают создание текстур для OpenGL ES благодаря использованию функций Android Canvas API и позволяют загружать текстуры из файлов PNG, JPG и GIF. Экземпляры Bitmap используются для размещения отрисованных поверхностей Android Canvas API. Класс GLUtils позволяет преобразовывать изображения из растрового формата в текстуры OpenGL ES. Благодаря этой интеграции с помощью Canvas API можно отрисовывать 2D-изображения, используемые затем в качестве текстур в OpenGL ES. Это особенно полезно при создании графических элементов, которые невозможно создать в OpenGL ES, например виджетов графического интерфейса и шрифтов. Но для использования этих функций, конечно, необходимо написать новый код Java.

Класс Bitmap был разработан прежде всего для использования с Canvas API, и при его использовании для загрузки текстур в OpenGL ES возникает несколько серьезных ограничений. Функции Canvas API соответствуют спецификации наложения по альфа-каналу Портера-Даффа, и класс Bitmap оптимизирует изображения со значениями альфа-канала для каждого пикселя, сохраняя их в формате с премультипликацией (A, R*A, G*A, B*A). Этот механизм подходит для алгоритма Портера-Даффа, но не для OpenGL, где требуется формат матрицы без премультипликации (ARGB). Это означает, что класс Bitmap можно использовать только с полностью непрозрачными текстурами (или с текстурами без значений альфа-канала для каждого пикселя). В трехмерных играх обычно требуются текстуры с альфа-каналом, поэтому вместо растровых изображений следует использовать для загрузки текстур байтовые массивы или NDK.

Еще одним недостатком класса Bitmap является поддержка загрузки только изображений в формате PNG, JPG или GIF, тогда как в играх OpenGL обычно используются сжатые форматы текстур, которые распаковываются графическим процессором и часто зависят от его архитектуры, например ETC1 и PVRTC. Классы Bitmap и GLUtils не поддерживают текстуры с зависимым форматом сжатия и мипмаппинг. Поскольку данные функции интенсивно используются в большинстве 3D-игр, это существенно усложняет портирование старых игр OpenGL на Android с использованием SDK. До тех пор, пока Google не решит эти проблемы, лучше всего просто отказаться от загрузки текстур с помощью классов Bitmap и GLUtils. Форматы текстур обсуждаются далее в разделе "Сжатые форматы текстур".

В Android ApiDemos содержится образец приложения с именем StaticTriangleRenderer, на примере которого показана загрузка непрозрачной текстуры из ресурсного файла PNG с использованием оболочки GLES10 для OpenGL ES 1.0 и классов GLSurfaceView, Bitmap и GLUtils. Аналогичная версия с именем GLES20TriangleRenderer, напротив, использует класс-оболочку GLES20 для OpenGL ES 2.0. Эти образцы служат хорошими отправными точками для разработки игр OpenGL ES, если вы рассчитываете использовать Android Framework SDK и классы-оболочки. Не используйте оригинальную версию с именем TriangleRenderer, поскольку в ней применяется оболочка для более старой версии привязок OpenGL ES к Java с именем javax.microedition.khronos.opengles. Компания Google выпустила новые привязки, чтобы обеспечить статический интерфейс для OpenGL ES специально под Android. У этих статических привязок более высокая производительность, они поддерживают больше функций OpenGL ES и позволяют использовать модель программирования, более близкую к использованию OpenGL ES с кодом C/C++, что упрощает повторное использование кода.

Android Framework SDK обеспечивает поддержку OpenGL ES с помощью нескольких привязок для Java из Google и Khronos, как показано в таблице 3.

Таблица 3. Сводная информация о классах-оболочках для OpenGL ES и образцах приложений

Привязки Java для OpenGL ES Описание Образцы приложений
javax.microedition.khronos.egl Стандартное определение Khronos
javax.microedition.khronos.opengles Стандартное определение Khronos TriangleRenderer, Kube, CubeMap
android.opengl Специальные статические интерфейсы для Android

Специальные статические привязки для Android имеют более высокую производительность и должны по мере возможности использоваться вместо привязок Khronos. Статические привязки предоставляют соответствующие классы-оболочки для всех версий OpenGL ES, доступных для разработки приложений под Android. Эти классы перечислены в таблице 4.

Таблица 4. Сводная информация о классах-оболочках Android для OpenGL ES и образцах приложений

Версия API Класс Java Образцы приложений
OpenGL ES 1.0 android.opengl.GLES10 StaticTriangleRenderer, CompressedTexture
OpenGL ES 1.0 android.opengl.GLES10Ext
OpenGL ES 1.1 android.opengl.GLES11
OpenGL ES 1.1 android.opengl.GLES11Ext
OpenGL ES 2.0 android.opengl.GLES20 GLES20TriangleRenderer, BasicGLSurfaceView, HelloEffects

Эти классы-оболочки устроены так, что большинство вызовов OpenGL ES в старом коде C/C++ можно преобразовать в Java, просто добавив к именам функций и символов OpenGL ES в виде префикса класс-оболочку подходящей версии API. См. примеры в таблице 5.

Таблица 5. Примеры преобразования вызовов OpenGL ES из C в Java

Язык C Язык Java
glDrawArrays(GL_TRIANGLES, 0, count) GLES11.glDrawArrays(GLES11.GL_TRIANGLES, 0, count)
glDrawArrays(GL_TRIANGLES, 0, count) GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, count)

При портировании игр OpenGL на Android с помощью этих классов-оболочек имеются три значительных ограничения: дополнительное потребление ресурсов средой Java Virtual Machine и JNI и работа по преобразованию старого кода C/C++ в Java. Java представляет собой интерпретируемый язык, и любой код Java на Android выполняется в виртуальной машине Dalvik и поэтому работает медленнее, чем скомпилированный код C/C++. Поскольку драйверы OpenGL ES всегда выполняются нативно, каждый вызов функции OpenGL ES через эти оболочки влечет за собой дополнительное потребление ресурсов интерфейсом JNI, что ограничивает производительность отрисовки игровой графики. Чем больше вызовов OpenGL ES выполняет ваше приложение, тем больше ресурсов потребляет интерфейс JNI. К счастью, структура OpenGL ES помогает минимизировать число необходимых обычно вызовов в ресурсоемких циклах отрисовки. Если производительность действительно ухудшается, вы всегда можете преобразовать ресурсоемкий код в C/C++ с помощью NDK. Разумеется, если изначально код был написан на C/C++, лучше всего сразу выбрать вариант с NDK.

Должны ли вы преобразовывать код C/C++ в Java — это зависит от особенностей вашего проекта. Если кода C/C++ сравнительно немного, он понятен и у вас нет задачи достичь сверхпроизводительности, то преобразование кода в Java будет оправданным. Иначе при большом объеме малопонятного кода C/C++ и высоких требованиях к производительности лучше использовать NDK.

Использование Android NDK

В июне 2009 года компания Google представила пакет NDK для Android, позволяющий выполнять в приложениях код C/C++ как нативный с более высокой производительностью по сравнению с Java. А поскольку большая часть имеющегося кода OpenGL написана на C или C++, в NDK предусмотрен более простой способ портирования приложений, особенно при наличии больших объемов кода C/C++, преобразование которых в Java нецелесообразно. Фактически, основная причина, по которой компания Google решила сделать пакет NDK общедоступным — это упрощение портирования игр OpenGL на Android. Благодаря этим преимуществам NDK становится наиболее популярным вариантом реализации приложений, требующих быстрой отрисовки графики в Android.

Пакет NDK позволяет компилировать код C/C++ в библиотеки общих объектов Linux, статично привязанные к приложению для Android. Библиотеки собираются с помощью средств GNU, которые предоставляются в пакете NDK от компании Google и могут запускаться в системах разработки для Windows, Mac OS* X или Linux при использовании интегрированной среды разработки Eclipse* или интерфейса командной строки. Этот набор средств поддерживает три процессорные архитектуры: ARM, Intel Atom (x86) и MIPS. Доступны все возможности C/C++, но большинство функций Linux API недоступно. Фактически непосредственная поддержка распространяется на API-интерфейсы OpenGL ES, OpenMAX* AL, OpenSL ES*, zlib, API ввода-вывода файлов Linux и немногие другие, которые Google относит к категории стабильных API-интерфейсов. Однако доступна документация по портированию других библиотек Linux в проект NDK, если это необходимо.

NDK позволяет произвольно распределять код между Java и C/C++ в соответствии с требованиями вашего приложения. В NDK поддерживается механизм вызова кода C/C++ из Java, называемый Java Native Interface. Но вызовы JNI приводят к дополнительному расходованию ресурсов, поэтому важно соблюдать осторожность при распределении приложений и использовать нативный код для минимизации вызовов через JNI. Обычно для наилучшей производительности и простоты портирования большая часть кода OpenGL ES остается на C/C++, а новый код Java пишется для использования класса GLSurfaceView и других классов SDK с целью управления событиями жизненного цикла приложения и поддержки других игровых функций. Android поддерживает JNI для процессоров Intel Atom начиная с NDK редакции 6b.

Пакет NDK поддерживает OpenGL ES 1.1 и 2.0 и включает в себя образцы приложений для обеих версий, которые также демонстрируют сочетание функции C и Java при использовании JNI. Эти приложения отличаются распределением кода между Java и C и реализацией многопоточности. Все эти приложения используют NDK и нативный код C, но в образце native-media весь код OpenGL ES остается на языке Java, в образцах san-angeles и native-activity весь код OpenGL ES на языке C, а в образце hello-gl2 код EGL и OpenGL ES распределен между Java и C. Использовать образец hello-gl2 не рекомендуется, поскольку в нем присутствует это разделение и не реализован предпочтительный метод конфигурирования GLSurfaceView для поверхностей OpenGL ES 2.0, состоящий в вызове метода setEGLContextClientVersion(2). См. в таблице 6.

Таблица 6. Сводная информация об образцах приложений OpenGL ES в NDK

Используемый API-интерфейс Образец приложения Распределение между SDK и NDK
OpenGL ES 1.1 san-angeles Весь код EGL и OpenGL ES на C.
OpenGL ES 1.1 native-activity Весь код на C, и используется класс NativeActivity.
OpenGL ES 2.0 hello-gl2 Настройка EGL на Java, а код OpenGL ES на C.
OpenGL ES 2.0 native-media Весь код EGL и OpenGL ES на Java.

Образец bitmap-plasma не использует OpenGL ES, но он также интересен тем, что демонстрирует использование библиотеки jnigraphics для реализации нативных функций, непосредственно обрабатывающих пиксели растровых изображений Android.

Примечание. Пакет Android NDK можно загрузить по адресу http://developer.android.com/tools/sdk/ndk/index.html.

События жизненного цикла Activity и многопоточность

Android требует, чтобы все вызовы OpenGL ES выполнялись из одного потока, поскольку контекст EGL может быть связан только с одним потоком, а отрисовка графики в главном потоке пользовательского интерфейса категорически не приветствуется. Лучше создать отдельный специальный поток для всего вашего кода OpenGL ES, который всегда будет выполняться из одного этого потока. Если в вашем приложении используется класс GLSurfaceView, этот выделенный поток отрисовки OpenGL ES создается автоматически. В противном случае приложение должно будет само создать поток отрисовки.

В образцах приложений san-angeles и native-activity весь код OpenGL ES на C сохранен, но в san-angeles используется часть кода Java и класс GLSurfaceView для создания потока отрисовки и управления жизненным циклом Activity, а в native-activity кода Java нет. Управление событиями жизненного цикла Activity осуществляется на C без использования GLSurfaceView, и используется поток отрисовки, предоставляемый классом NativeActivity. NativeActivity — это удобный класс из NDK, позволяющий реализовать обработчики событий жизненного цикла Activity в виде нативного кода, например onCreate(), onPause() и onResume(). К некоторым службам и поставщикам контента Android невозможно получить непосредственный доступ из нативного кода, но можно реализовать это через JNI.

Образец native-activity является хорошей отправной точкой для портирования игр OpenGL ES, поскольку в нем показано использование класса NativeActivity и статической библиотеки android_native_app_glue для обработки событий жизненного цикла в нативном коде. Эти классы предоставляют отдельный поток отрисовки для кода OpenGL ES, поверхность отрисовки и окно на экране, поэтому вам не нужны классы GLSurfaceView и TextureView. Основной точкой входа этого нативного приложения является функция android_main(), выполняемая в своем собственном потоке и имеющая свой собственный цикл событий, позволяющий принимать входящие события. К сожалению, NDK не включает в себя версию этого образца для OpenGL ES 2.0, но вы можете заменить весь код версии 1.1 в этом образце на свой код версии 2.0.

Приложения, использующие NativeActivity, должны выполняться в Android 2.3 или более поздней версии и содержать в своем файле манифеста специальные декларации, описанные во второй части данной серии статей.

Java Native Interface

Если вы реализуете свое приложение преимущественно на C/C++, вам будет сложно избежать использования некоторых классов Java в случае крупных и более профессиональных проектов. Это, например, доступные только в SDK API-интерфейсы Android AssetManager и Resources, являющиеся предпочтительными для обработки интернационализации, различных размеров экрана и других ситуаций. Интерфейс JNI также является хорошим решением, поскольку он позволяет вызывать не только функции C/C++ из кода Java, но и классы Java из кода C/C++. Поэтому, несмотря на дополнительное расходование ресурсов, интерфейсом JNI не следует пренебрегать. Он обеспечивает наилучший способ доступа к важным системным функциям, предлагаемым только в SDK, особенно когда для этих функций не обязательно добиваться высокой производительности. Мы не будем приводить в данной статье полное описание использования JNI, но выделим три основных этапа, необходимых для создания вызова C/C++ из Java с помощью JNI:

  1. Добавление объявления функции C/C++ в файл класса Java в качестве нативного типа.
  2. Добавление статического инициализатора библиотеки общих объектов, содержащей нативную функцию.
  3. Добавление функции с соответствующим именем, согласованным с применяемой схемой именования, в файл с нативным исходным кодом программы.

Примечание. Дополнительные сведения об использовании JNI с NDK см. по адресу http://developer.android.com/training/articles/perf-jni.html.

OpenGL ES 1.1 или 2.0?

Итак, на какую версию OpenGL ES вы должны ориентироваться в приложении для Android? Версия OpenGL ES 1.0 была заменена на 1.1, поэтому выбирать приходится только из двух версий: 1.1 и 2.0. Компании Khronos и Google, возможно, будут поддерживать обе эти версии в течение неограниченного срока, но OpenGL ES 2.0 по многим параметрам превосходит версию 1.1. Она гораздо более универсальна и обеспечивает более высокую производительность благодаря функциям программирования шейдеров (обработчиков теней) в языке GLSL (OpenGL Shading Language) ES. В ней даже может быть снижена потребность к коде и памяти для текстур. Но у Khronos и Google остается, по крайней мере, одна веская причина для того, чтобы продолжать поддержку версии 1.1, — эта версия по внешнему виду намного ближе к оригинальному стандарту OpenGL 1.x, применяемому в настольных ПК и игровых консолях во всем мире на протяжении десятилетий. Поэтому старые игры может быть проще портировать на OpenGL ES 1.1, чем на 2.0; и, чем старее игра, тем вероятность этого выше.

Если в портируемой игре нет кода шейдера, можно выбрать OpenGL ES версии как 1.1, так и 2.0, хотя работать с версией 1.1, вероятно, будет проще. Если в игре уже есть код шейдера, то, безусловно, лучше выбрать OpenGL ES 2.0, особенно если учесть, что в последних версиях Android используется преимущественно версия 2.0. Как сообщает компания Google, по состоянию на октябрь 2012 года более 90 процентов устройств Android, обращающихся на веб-сайт Google Play, поддерживают OpenGL ES версии как 1.1, так и 2.0.

Примечание. Дополнительные сведения см. по адресу http://developer.android.com/about/dashboards/index.html.

Заключение

Отрисовку графики OpenGL ES на Android можно реализовать с помощью Android SDK, NDK или их комбинации с использованием JNI. При использовании пакета SDK потребуется написание кода на Java, и этот способ лучше всего подходит для разработки новых приложений, тогда как NDK делает целесообразным портирование уже имеющегося кода OpenGL на языке C/C++. Для большинства проектов портирования игр потребуется сочетание компонентов SDK и NDK. В новых проектах рекомендуется ориентироваться на версию OpenGL ES 2.0, а не 1.1, кроме случаев, когда имеющийся код OpenGL настолько старый, что не использует функции шейдеров GLSL.

Во второй части этой серии статей рассматриваются препятствия, существующие при портировании игр OpenGL, которые необходимо изучить, прежде чем начинать подобный проект, в том числе различия расширений OpenGL, поддержка операций с плавающей запятой, сжатые форматы текстур и библиотека GLU. Кроме того, здесь описана настройка системы разработки для Android на базе процессоров Intel Atom и пути достижения максимально возможной производительности в средствах эмуляции виртуального устройства Android при использовании OpenGL ES.

Об авторе

Клэй Д. Монтгомери (Clay D. Montgomery) является одним из ведущих разработчиков драйверов и приложений для OpenGL во встроенных системах. Он участвовал в разработке аппаратных графических ускорителей, графических драйверов, API-интерфейсов и приложений OpenGL для различных платформ в компаниях STB Systems, VLSI Technology, Philips Semiconductors, Nokia, Texas Instruments, AMX и в качестве независимого консультанта. Он сыграл важную роль в разработке некоторых первых драйверов OpenGL ES, OpenVG* и SVG, приложений для платформ Freescale i.MX и TI OMAP* и графических ядер Vivante, AMD и PowerVR*. Кроме того, он подготовил и провел ряд семинаров по разработке на базе OpenGL ES во встроенных системах Linux и являлся представителем нескольких компаний консорциума Khronos Group.