OpenGL ES 1.1 в Windows 8 и Windows Phone 8.1

69043dc597364141bcd8d80fbd3b0151.pngВ далеком 1998 году я пытался сделать свою игру с OpenGL. Разработка с трудом дошла до альфы и была заброшена, но что особо запомнилось, так это как удобно было делать под GL интерфейсы — ортогональная проекция, пара трансформаций, биндинг нескольких вершин с GL_TRIANGLE_STRIP и у нас уже есть кнопка. И вот, спустя шестнадцать лет и занимаясь мобильным игростроем я столкнулся с таким же подходом в OpenGL ES 1.*, разве что 2D текстуры без вращений можно теперь рисовать через glDrawTexfOES.Я поддерживал несколько проектов, сделанных по этому принципу и понемногу в голове выстроился коварный план: сделать кросс-платформенную 2D игру на мобильных с OpenGL ES и на C#, а на десктопах с обычным OpenGL. Цели я добился не с первого раза и было с этим много проблем, но в результате очередной проект у меня работает без изменений бизнес-логики на iOS, Android, BlackBerry, Windows XP/7, Mac OS X, Linux, ReactOS, Windows 8, Windows Phone 8.1. Материала набралось на много статей, но в этот раз я расскажу именно о поддержке Windows Runtime.OpenTKba575267233847ed9e6123678b9f8e93.pngМожно много спорить на счет удобства OpenGL именно для 2D, до хрипоты в горле убеждать себя, что для полноценной игры необходимы шейдеры и многопроходный рендеринг, а заодно и находить подтверждения, что устаревший OpenGL ES 1.1 часто реализован именно на уровне эмуляции через шейдеры. Это я оставлю для Дон Кихотов и теоретиков. Меня же волновало, что это самый простой способ написать единожды код 2D отрисовки и запускать его на разных платформах, причем не используя монструозные Unity, MonoGame и другие движки.На iOS и Android под Xamarin все прошло гладко, работа с GL делается через библиотеку OpenTK с неймспейсом OpenGL.Graphics.GL11, константы и методы на обеих платформах одинаковы. На десктопах я решил использовать OpenTK.Graphics.OpenGL, т.е. обычный десктопный OpenGL с C# оберткой. Там в принципе нет glDrawTexfOES, но без проблем можно сделать замену для него и рисовать два треугольника через GL_TRIANGLE_STIP/GL_TRIANGLES и glDrawElements — по сравнению с мобильными, производительности хватает с лихвой и VBO тут не нужны.Пример враппера с GL_TRIANGLES private static readonly int[] s_textureCropOesTiv = new int[4]; private static readonly short[] s_indexValues = new short[] { 0, 1, 2, 1, 2, 3 }; private static readonly float[] s_vertexValues = new float[] { -0.5f, 0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f }; public void glDrawTexfOES (float x, float y, float z, float w, float h) { glPushMatrix (); glLoadIdentity ();

glTranslatef (w / 2.0f + x, h / 2.0f + y, 0.0f);

glScalef (w, -h, 1.0f);

int[] tiv = s_textureCropOesTiv; // NOTE: clip rectangle, should be set before call

int[] texW = new int[1]; glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, texW); int[] texH = new int[1]; glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, texH);

float[] texCoordValues = new float[8];

float left = 1.0f — (tiv[0] + tiv[2]) / (float)texW[0]; float bottom = 1.0f — tiv[1] / (float)texH[0]; float right = 1.0f — tiv[0] / (float)texW[0]; float top = 1.0f — (tiv[1] + tiv[3]) / (float)texH[0];

texCoordValues[0] = right; texCoordValues[2] = left; texCoordValues[4] = right; texCoordValues[6] = left;

texCoordValues[1] = bottom; texCoordValues[3] = bottom; texCoordValues[5] = top; texCoordValues[7] = top;

glEnableClientState (GL_VERTEX_ARRAY); glEnableClientState (GL_TEXTURE_COORD_ARRAY); glVertexPointer (2, GL_FLOAT, 0, s_vertexValues); glTexCoordPointer (2, GL_FLOAT, 0, texCoordValues); glDrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, s_indexValues);

glPopMatrix (); } Учтите, что копипастить себе этот код не стоит — он не будет работать там, где нет констант GL_TEXTURE_WIDTH/GL_TEXTURE_HEIGHT. Заодно переменная s_textureCropOesTiv должна быть заполнена до вызова, а сам код не выполняет переворот вьюпорта по оси ординат. XAML Некоторое количество магии понадобилось, чтобы проект запускался на актуальных версиях Mono, .Net 2.0–4.5, Wine, а заодно и под ReactOS, но в целом кроме зоопарка с текстурами особых проблем тут не было. А вот проблемы начались на Windows 8 и Windows Phone, где OpenGL отсутствует в принципе. С начала я пробовал решить это малой кровью, буквально дописав свою версию glDrawTexfOES, которая бы внутри вызывала что-то специфичное для этих систем. В ходе экспериментов я использовал XAML элемент Canvas, а в нем рисовал Rectangle, у которого в Brush использовалась нужная трансформация для отображения только части текстуры.Код трансформации в XAML TransformGroup group = new TransformGroup ();

ScaleTransform scale = new ScaleTransform (); scale.ScaleX = (double)texture.PixelWidth / (double)clipRect.Width; scale.ScaleY = (double)texture.PixelHeight / (double)clipRect.Height; group.Children.Add (scale);

TranslateTransform translate = new TranslateTransform (); translate.X = -scale.ScaleX * (double)clipRect.X / (double)texture.PixelWidth; translate.Y = -scale.ScaleY * (double)clipRect.Y / (double)texture.PixelHeight; group.Children.Add (translate);

imageBrush.RelativeTransform = group; clipRect — прямоугольник с параметрами обрезки, аналог s_textureCropOesTiv из примера вышеtexture — BitmapSource с самой текстурой Этот метод кажется странным, но надо помнить, что XAML зачастую hardware accelerated и довольно быстр. Я портировал с таким подходомнесколько мобильных OpenGL ES игр на Windows 8 и работают они приемлемо, только нет возможности изменять цвет текстур, как в GL через glColor. Т.е. в принципе в XAML разрешается менять прозрачность элемента, но никак нельзя менять его Color Tint. Например, если у вас используются белые шрифты и потом раскрашиваются в разные цвета, то с этим подоходом они так и останутся белыми.В целом, вариант с XAML достаточно сомнителен и не совсем соответствовал изначальному плану, да и без цветовой дифференциации штанов модуляции, потому когда игра была на 80% готова и уже работала на мобильных и стационарном .Net/Mono, я начал искать более приемлемые варианты для Windows 8. Много было слухов и восторгов вокруг порта библиотеки Angle, но на тот момент она была уж очень сырая и без поддержки C#. Напрямую из C# работать с DirectX оказалось тоже не возможно, а сама Microsoft предлагает разработчику несколько «простых» путей: переделать весь C# код на C++, использовать стороннюю библиотеку SharpDX (C# биндинг над DirectX), либо перейти на MonoGame. Библиотека MonoGame это наследник XNA, использующий ту же SharpDX для вывода графики на Windows 8, она довольно неплоха, но довольно специфична и переходить на нее в моем проекте было поздновато. SharpDX выглядел не менее монструозным, ведь тянет за собой все существующие возможности DirectX, хотя и довольно близок к тому, что мне было надо. Я уже начал проводить с ним серьезные беседы с паяльником и мануалом, когда наткнулся на проект gl2dx.

GL2DX Библиотека эта была выложена юзером average на CodePlex несколько лет назад и больше не обновлялась. Это С++ библиотека, которая объявляет такие же функции, как в OpenGL, а внутри транслирует их в вызовы D3D11. К библиотеке шел пример на C++/CX, который создавал XAML страницу с SwapChainBackgroundPanel и инициализировал ее через D3D11CreateDevice для работы с С++ частью. Проект был бы хорош, если бы хоть немного вышел из стадии прототипа. Технически, в нем работает всего несколько процентов OpenGL методов, а в остальных стоят ассерты. С другой стороны, она справляется с выводом 2D текстур, трансформацией и простейшей геометрией. На этом этапе я взялся за библиотеку и довел ее до состояния продукта, который подключается к C# проекту как Visual Studio Extension и позволяет писать подобный код: Код GL.Enable (All.ColorMaterial); GL.Enable (All.Texture2D); GL.Color4(1.0f, 1.0f, 1.0f, 1.0f);

GL.TexParameter (All.Texture2D, All.TextureCropRectOes, new int[] { 0, 0, 1024, 1024 }); GL.BindTexture (All.Texture2D, m_textureId1); GL.DrawTex (0, — (m_width — m_height) / 2, 0, m_width, m_width); for (int i = 0; i < 10; i++) { if (i % 2 == 0) { GL.BindTexture(All.Texture2D, m_textureId2); GL.TexParameter(All.Texture2D, All.TextureCropRectOes, new int[] { 0, 0, 256, 256 }); } else { GL.BindTexture(All.Texture2D, m_textureId2); GL.TexParameter(All.Texture2D, All.TextureCropRectOes, new int[] { 256, 0, 256, 256 }); } int aqPadding = 20; int fishSize = 128; int aqWidth = (int)m_width - aqPadding * 2 - fishSize;

float x = (Environment.TickCount / (i + 10)) % aqWidth; float alpha = 1.0f; if (x < fishSize) alpha = x / fishSize; else if (x > aqWidth — fishSize) alpha = (aqWidth — x — fishSize) / 128.0f;

GL.Color4(1.0f, 1.0f, 1.0f, alpha); GL.DrawTex (x + aqPadding, m_height / 20 * (i + 5), 0, fishSize, fishSize); } P.S. Код в формате вызовов OpenTK, что немного сбивает с толку тех, что привык писать glColor4f вместо GL.Color4. Сие поделие получило от меня гордое название MetroGL.MetroGL 9cf6cded1adb43d8a6a82b4f11c163d5.jpgПример на C++/CX трансформировался в библиотеку на этом же птичьем современном языке, оброс большим количеством дополнительных функций, а C++ получила реализацию многих OpenGL методов, блендинги, оптимизацию внутренного VertexBuilder, загрузку произвольных изображений и DDS текстур, а главное — точную имитацию glDrawTexfOES, дающую 1в1 такую же картинку, как на OpenGL ES, а заодно соединяющую последовательные операции с одной текстурой в единый DrawCall. Кое-что пришлось доводить напильником, сам код местами грязноват (как до меня, так и после), а для создания VSIX расширения надо пересобирать проект вручную под каждую архитектуру (x86, x64, ARM) и лишь потом билдить VSIX проект. Главное, что если у вас есть OpenGL ES 1.* код с 2D интерфейсом или не сложным 3D, то с этой библиотекой его можно использовать прямо из C#, не думая о внутренностях, С++ коде, D3D11 контекстах и других гадостяхрадостях. Заодно сделан пример с рыбками справа и кодом из под ката. Конечно, если код у вас на OpenGL 2.0+ с шейдерами и экстеншенами, то ни о каком портировании речи и не будет.Другой неприятный момент в том, что у меня нет настроения и желания доводить библиотеку до уровня 50–100% совместимости с OpenGL, а значит под ваши конкретные задачи ее придется затачивать своими силами. Благо, весь код выложен на github и я пока никуда не исчезаю и буду рад коммитам или вообще желающим взвалить на себя этот груз. Библиотека собирается под Windows 8.0 и Windows Phone 8.1, для VSIX может понадобиться не-Express версия Visual Studio.Эпилог Ну и в конце-концов немного об играх. Проект свой я на 100% закончил и именно комбинация C# и OpenGL дала возможность сделать высокоуровневый код вообще не изменяемым — это библиотека без единого дефайна, не использующая каких-либо системных вызовов. Затем идет код среднего уровня: рисование через OpenGL в 2D, с вращением, трансформацией и цветовой модуляцией, тут немного код отличается на разных платформах — разные текстуры, по-разному хранятся данные. Низкоуровневая часть уже для каждой платформы разная, это создание окна, инициализация контекста, вывод звука. В любом случае, девять платформ, перечисленные в начале статьи, реально работают, и пусть C# в связке с OpenGL пока нельзя использовать в вебе или на Firefox OS, но все-равно разве это не отблеск кроссплатформенного будущего, господа? 421a19386b9048db8c3c79578d70f212.jpg

© Habrahabr.ru