![](https://habrastorage.org/files/690/43d/c59/69043dc597364141bcd8d80fbd3b0151.png)
Я поддерживал несколько проектов, сделанных по этому принципу и понемногу в голове выстроился коварный план: сделать кросс-платформенную 2D игру на мобильных с OpenGL ES и на C#, а на десктопах с обычным OpenGL. Цели я добился не с первого раза и было с этим много проблем, но в результате очередной проект у меня работает без изменений бизнес-логики на iOS, Android, BlackBerry, Windows XP/7, Mac OS X, Linux, ReactOS, Windows 8, Windows Phone 8.1. Материала набралось на много статей, но в этот раз я расскажу именно о поддержке Windows Runtime.
OpenTK
![](https://habrastorage.org/files/ba5/752/672/ba575267233847ed9e6123678b9f8e93.png)
На iOS и Android под Xamarin все прошло гладко, работа с GL делается через библиотеку OpenTK с неймспейсом OpenGL.Graphics.GL11, константы и методы на обеих платформах одинаковы. На десктопах я решил использовать OpenTK.Graphics.OpenGL, т.е. обычный десктопный OpenGL с C# оберткой. Там в принципе нет glDrawTexfOES, но без проблем можно сделать замену для него и рисовать два треугольника через GL_TRIANGLE_STIP/GL_TRIANGLES и glDrawElements — по сравнению с мобильными, производительности хватает с лихвой и VBO тут не нужны.
Пример враппера с GL_TRIANGLES
Учтите, что копипастить себе этот код не стоит — он не будет работать там, где нет констант GL_TEXTURE_WIDTH/GL_TEXTURE_HEIGHT. Заодно переменная s_textureCropOesTiv должна быть заполнена до вызова, а сам код не выполняет переворот вьюпорта по оси ординат.
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
clipRect — прямоугольник с параметрами обрезки, аналог s_textureCropOesTiv из примера выше
texture — BitmapSource с самой текстурой
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 достаточно сомнителен и не совсем соответствовал изначальному плану, да и без цветовой
GL2DX
Библиотека эта была выложена юзером average на CodePlex несколько лет назад и больше не обновлялась. Это С++ библиотека, которая объявляет такие же функции, как в OpenGL, а внутри транслирует их в вызовы D3D11. К библиотеке шел пример на C++/CX, который создавал XAML страницу с SwapChainBackgroundPanel и инициализировал ее через D3D11CreateDevice для работы с С++ частью. Проект был бы хорош, если бы хоть немного вышел из стадии прототипа. Технически, в нем работает всего несколько процентов OpenGL методов, а в остальных стоят ассерты. С другой стороны, она справляется с выводом 2D текстур, трансформацией и простейшей геометрией. На этом этапе я взялся за библиотеку и довел ее до состояния продукта, который подключается к C# проекту как Visual Studio Extension и позволяет писать подобный код:
Код
P.S. Код в формате вызовов OpenTK, что немного сбивает с толку тех, что привык писать glColor4f вместо GL.Color4.
Сие поделие получило от меня гордое название MetroGL. 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
![](https://habrastorage.org/files/9cf/6cd/ed1/9cf6cded1adb43d8a6a82b4f11c163d5.jpg)
Другой неприятный момент в том, что у меня нет настроения и желания доводить библиотеку до уровня 50-100% совместимости с OpenGL, а значит под ваши конкретные задачи ее придется затачивать своими силами. Благо, весь код выложен на github и я пока никуда не исчезаю и буду рад коммитам или вообще желающим взвалить на себя этот груз. Библиотека собирается под Windows 8.0 и Windows Phone 8.1, для VSIX может понадобиться не-Express версия Visual Studio.
Эпилог
Ну и в конце-концов немного об играх. Проект свой я на 100% закончил и именно комбинация C# и OpenGL дала возможность сделать высокоуровневый код вообще не изменяемым — это библиотека без единого дефайна, не использующая каких-либо системных вызовов. Затем идет код среднего уровня: рисование через OpenGL в 2D, с вращением, трансформацией и цветовой модуляцией, тут немного код отличается на разных платформах — разные текстуры, по-разному хранятся данные. Низкоуровневая часть уже для каждой платформы разная, это создание окна, инициализация контекста, вывод звука. В любом случае, девять платформ, перечисленные в начале статьи, реально работают, и пусть C# в связке с OpenGL пока нельзя использовать в вебе или на Firefox OS, но все-равно разве это не отблеск кроссплатформенного будущего, господа?
![](https://habrastorage.org/files/421/a19/386/421a19386b9048db8c3c79578d70f212.jpg)