3. Что скрывается за GLSurfaceView или библиотека EGL. Подробный разбор инициализации OpenGL ES.За
GLSurfaceView скрывается инициализация OpenGL с помощью библиотеки EGL.
EGL - интерфейс между API графического адаптера, таких как
OpenGL ES и
OpenVG и системой управления окнами платформы. EGL также обеспечивает возможность взаимодействия между API для эффективной передачи данных – например между видеоподсистемой работающей c OpenMAX AL и GPU работающем с OpenGL ES.
EGL предоставляет механизмы для создания поверхности ( Surface ), на которой клиент API, такой как OpenGL ES или OpenVG создает графику. EGL синхронизирует клиент API и родной API визуализации для платформы ( в случае Android это Skia). Это позволяет бесшовную, высокопроизводительную , ускоренную визуализацию с использованием как OpenGL ES так и OpenVG для смешанного режима 2D и 3D-рендеринга.
Cистема управления окнами ( native platform window system ) - оконная система, обеспечивающая стандартные инструменты и протоколы для построения графического интерфейса пользователя. В случае Android это SurfaceFlinger, для *nix это обычно X Window System, MacOs и Windows используют свою, ни с чем не совместимую систему.
В ранних реализациях стандарта OpenGL был пропуск в этапе создания GLSurface поверхности в контексте оконной системы.
Для инициализации приходилось использовать средства самой операционной системы, которые сильно отличались друг от друга и даже не всегда была возможность обеспечить одинаковый вид и функциональность
Нестандартных реализаций было достаточно много — это SDL, GLUT,CPW,NGL и т.д.
Но все же единого стандарта не было.
Библиотека EGL создана для того чтобы закрыть этот пробел.
OpenVG - стандартный API, предназначенный для аппаратно-ускоряемой двухмерной
векторной графики. Так как на платформе Android этот API не задействован ( даже странно, почему так ) рассматривать его не буду. =(
Посмотрим что же делает
GLSurfaceView( исходник ):
( Выделяю только ключевые моменты )
public class GLSurfaceView extends SurfaceView
Из этой строчки видно что GLSurfaceView является расширением SurfaceView.
SurfaceView предоставляет выделенные поверхности для рисования, встроеную в иерархию Activity. Вы можете управлять форматом этой поверхности и, если хотите, ее размер; SurfaceView заботится о размещении поверхности в нужное место на экране. Соответственно можно размещать view-шки как на обычном SurfaceView или например встроить GLSurfaceView в свой элемент управления.
...
holder.setFormat(PixelFormat.RGB_565);
...
Далее GLSurfaceView задает формат своей поверхности как RGB_565 ( 16bit цвет ).
На это стоит обратить внимание, подробности чуть ниже.
Теперь сама инициализация OpenGL:
...
EGL10 Egl = (EGL10) EGLContext.getEGL();
...
Получение экземпляра враппера EGL.
wrapper ( враппер ) - обёртка библиотеки, является промежуточным слоем между прикладной программой и другой библиотекой или интерфейсом программирования приложений (API).
Целью написания обёртки библиотеки может быть обеспечение работоспособности библиотеки (API) в каком-либо (чаще скриптовым) языке, в котором прямой вызов функций этой библиотеки API затруднителен или невозможен.
В Android классы GLES и EGL являются обертками к нативным библиотекам OpenGL и EGL.
...
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
...
Получение ссылки на стандартный дисплей.
Дисплеев в системе может быть несколько, если нужен доступ к другим дисплеем константу EGL_DEFAULT_DISPLAY нужно заменить на соответствующею. Все константы кроме EGL_DEFAULT_DISPLAY платформозависимы.
...
int] version = new int[2];
mEgl.eglInitialize(mEglDisplay, version);
...
Инициализация EGL для конкретного дисплея.
Возвращает версию EGL в массиве version].
...
EGLConfig mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
...
Выбирается конфигурация OpenGL поверхности.
Это самый важный момент в инициализации OpenGL.
Именно на этом этапе можно задать глубину цвета, буферов, антиалайзинг и еще кучу параметров.
Ниже будет очень подробное описание.
...
mEglContext = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,mEGLContextClientVersion != 0 ? attrib_list : null);
...
Создаем контекст определенной конфигурации.
Очень "тяжелая" операция.
Именно в этот момент начинает работать часть графического драйвера отвечающего за 3D,
происходит куча проверок, инициализируются большие объемы памяти и т.д.
Все, OpenGL инициализирован.
Теперь создание GLSurface:
...
egl.eglCreateWindowSurface(display, config, nativeWindow, null);
...
Cоздается одним вызовом. nativeWindow - ссылка на поверхность/окно где создастся OpenGLSurface.
( прошу обратить внимание на то что этот метод можно вызвать только из SurfaceView или его наследников из-за ограничения во враппере EGL )
И устанавливает текущей контекст:
...
mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext);
...
Вот и все. Можно рисовать.
OpenGL контекст создается за 7 вызовов EGL.
Все выше приводилось для понимания процесса инициализации.
Не буду приводить полный рабочий пример инициализации, так как заниматься изобретением велосипедов не имеет смысла и основа приложения
в дальнейших примерах будет GLSurfaceView, с небольшими изменениями.
еще одна необходимая функция EGL - смена кадров ( свап буферов ):
...
mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)
...
Тут я думаю все понятно.
Теперь разберемся с конфигурациями GLSurface.
OpenGLSurface конфигурации - это возможность создания OpenGLSurface с разными параметрами, такими как глубина цвета, сглаживание, глубина Z-буфера/трафорета и еще кучей параметров.
Для программиста шаблон конфигурации представляет из себя одномерный массив целых чисел с парами ключ-значение ( записанными последовательно ).
Массив всегда должен заканчивается ключом EGL10.EGL_NONE.
Пример такого массива:
int] configSpec = {
EGL10.EGL_RED_SIZE, 5,
EGL10.EGL_GREEN_SIZE, 6,
EGL10.EGL_BLUE_SIZE, 5,
EGL10.EGL_DEPTH_SIZE, 16,
EGL10.EGL_RENDERABLE_TYPE, 4,
EGL10.EGL_SAMPLE_BUFFERS, 1
EGL10.EGL_SAMPLES, 4,
EGL10.EGL_NONE
};
Возможные ключи конфигурации:
EGL_ALPHA_SIZE
EGL_ALPHA_MASK_SIZE
EGL_BIND_TO_TEXTURE_RGB
EGL_BIND_TO_TEXTURE_RGBA
EGL_BLUE_SIZE
EGL_BUFFER_SIZE
EGL_COLOR_BUFFER_TYPE
EGL_CONFIG_CAVEAT
EGL_CONFIG_ID
EGL_CONFORMANT
EGL_DEPTH_SIZE
EGL_GREEN_SIZE
EGL_LEVEL
EGL_LUMINANCE_SIZE
EGL_MAX_PBUFFER_WIDTH
EGL_MAX_PBUFFER_HEIGHT
EGL_MAX_PBUFFER_PIXELS
EGL_MAX_SWAP_INTERVAL
EGL_MIN_SWAP_INTERVAL
EGL_NATIVE_RENDERABLE
EGL_NATIVE_VISUAL_ID
EGL_NATIVE_VISUAL_TYPE
EGL_RED_SIZE
EGL_RENDERABLE_TYPE
EGL_SAMPLE_BUFFERS
EGL_SAMPLES
EGL_STENCIL_SIZE
EGL_SURFACE_TYPE
EGL_TRANSPARENT_TYPE
EGL_TRANSPARENT_RED_VALUE
EGL_TRANSPARENT_GREEN_VALUE
EGL_TRANSPARENT_BLUE_VALUE
...
+ еще существуют вендорозависимые ключи,
такие как EGL_COVERAGE_BUFFERS_NV ( сглаживание для чипов Tegra и других ).
Для нас важными являются только несколько ключей:
EGL_RED_SIZE - бит на красный канал
EGL_GREEN_SIZE -бит на зеленый канал
EGL_BLUE_SIZE - бит на синий канал
EGL_ALPHA_SIZE - бит на альфа канал
EGL_DEPTH_SIZE - глубина Z буфера
EGL_RENDERABLE_TYPE - API поддерживаемые в данной конфигурации. Значение - битовая маска, так как одной и той же конфигурации может соответствовать несколько API. OpenGL ES 2.0 соответствует значение 4
EGL_SAMPLE_BUFFERS - Поддержка антиалайзинга
EGL_SAMPLES - количество семплов на пиксель
EGL_NONE - завершение списка
Как это все работает:
// Создаем шаблон конфигурации с [B]минимальными[/B] требуемыми параметрами
int] configSpec = {
EGL10.EGL_RED_SIZE, 5, // минимум 16битный цвет
EGL10.EGL_GREEN_SIZE, 6,
EGL10.EGL_BLUE_SIZE, 5,
EGL10.EGL_DEPTH_SIZE, 16, // Глубина Z буффера минимум 16бита
EGL10.EGL_RENDERABLE_TYPE, 4, // поддержка GLES20
EGL10.EGL_SAMPLE_BUFFERS, 1, // поддержка антиалайзинга
EGL10.EGL_SAMPLES, 2, // минимум 2 семпла
EGL10.EGL_NONE
};
// Запрашиваем список подходящих конфигураций
//
mValue = new int[1];
egl.eglChooseConfig(display, configSpec, null, 0,mValue);
int numConfigs = mValue[0]; // получаем количество конфигов подходящих под наше описание.
// так как шаблон конфигурации задает МИНИМАЛЬНЫЕ требования
//то в данном случае в список попадут конфигурации и с 32битным цветом и со сглаживанием например на 4 ( или 8 ) сэмплов.
if(numConfigs <= 0){
EGLConfig] configs = new EGLConfig[numConfigs];
egl.eglChooseConfig(display, configSpec, configs, numConfigs,mValue); // Получаем список конфигураций.
// Теперь у нас есть заполненный массив конфигураций configs
}else{Конфигураций соответствующих шаблону не найдено.}
Важно:
Когда более чем одна конфигурация буфера кадра соответствует шаблону, возвращается список конфигураций. Список сортируется в соответствии со следующими правилами приоритета, которые применяются в порядке:
1.по EGL_CONFIG_CAVEAT, в следующем порядке: EGL_NONE, EGL_SLOW_CONFIG и EGL_NON_CONFORMANT_CONFIG.
Например разработчик устройства ( драйвера ) пометил определенные конфигурации как медленные или не рекомендуемые - они окажутся в конце списка.
2. по EGL_COLOR_BUFFER_TYPE, в порядке EGL_RGB_BUFFER, EGL_LUMINANCE_BUFFER ( монохромный ).
3. по сумме числа бит всех каналов RGBA ( или глубине EGL_LUMINANCE_SIZE ). То есть сначала в списке идут конфигурации с максимальной глубиной цвета и далее в порядке уменьшения. На это стоит обратить особое внимание, так как если вы запросили конфигурацию RGB565 без альфа-канала то первыми в списке скорее всего будут конфигурации RGBA8888 ( при одинаковых остальных параметрах ), так как сумма бит всех каналов в них больше.
4. EGL_BUFFER_SIZE в порядке возрастания( то есть сначала будут конфигурации с минимальным значением ).
5. EGL_SAMPLE_BUFFERS в порядке возрастания.
6. EGL_SAMPLES в порядке возрастания.
7. EGL_DEPTH_SIZE в порядке возрастания.
8. EGL_STENCIL_SIZE в порядке возрастания.
9. EGL_ALPHA_MASK_SIZE в порядке возрастания.
10. EGL_NATIVE_VISUAL_TYPE ( тут в зависимости от реализации, обычно одно значение ).
10. EGL_CONFIG_ID в порядке возрастания (последняя опция сортировки, гарантирующая уникальность).
Сортировка не производится по ключам:
EGL_BIND_TO_TEXTURE_RGB, EGL_BIND_TO_TEXTURE_RGBA, EGL_CONFORMANT, EGL_LEVEL, EGL_NATIVE_RENDERABLE, EGL_MAX_SWAP_INTERVAL, EGL_MIN_SWAP_INTERVAL, EGL_RENDERABLE_TYPE, EGL_SURFACE_TYPE, EGL_TRANSPARENT_TYPE, EGL_TRANSPARENT_RED_VALUE, EGL_TRANSPARENT_GREEN_VALUE, and EGL_TRANSPARENT_BLUE_VALUE.
Пример. Выводит на экран все доступные конфигурации для OpenGL ES 2.0
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import javax.microedition.khronos.egl.*;
public class MyActivity extends Activity {
final int EGL_COVERAGE_BUFFERS_NV = 0x30E0;
final int EGL_COVERAGE_SAMPLES_NV = 0x30E1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
TextView textv = new TextView(this); // TextView c ScrollView для отображения результата
LinearLayout.LayoutParams blp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PAREN
T);
ScrollView scrollv = new ScrollView(this);
scrollv.setLayoutParams(blp);
scrollv.addView(textv);
this.addContentView(scrollv,blp);
EGL10 Egl = (EGL10) EGLContext.getEGL(); // получаем враппер Egl
EGLDisplay EglDisplay = Egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); // получаем ссылку на дисплей
int] version = new int[2]; // массив для получения версии EGL
Egl.eglInitialize(EglDisplay, version); // Инициализация EGL
int] configSpec = { // шаблон конфигурации
EGL10.EGL_RENDERABLE_TYPE, 4, // поддержка GLES20
EGL10.EGL_NONE // конец
};
int] mValue = new int[1];
Egl.eglChooseConfig(EglDisplay, configSpec, null, 0,mValue); // получаем колличество конфигураций подходящих под шаблон
int numConfigs = mValue[0];
EGLConfig] configs = new EGLConfig[numConfigs];
int] num_conf = new int[numConfigs];
Egl.eglChooseConfig(EglDisplay, configSpec, configs, numConfigs,mValue); // получаем массив конфигураций
String text ="EGL version "+version[0]+"."+version[1]+"\n";
text+= printConfigs(configs,EglDisplay,Egl)+"\n";
textv.setText(text);
}
private String printConfigs(EGLConfig] conf,EGLDisplay EglDisplay,EGL10 Egl){
String text="";
for(int i = 0; i < conf.length; i++){
int] value = new int[1];
if (conf[i] != null)
{
text+="==== Config №"+i+" ====\n";
Egl.eglGetConfigAttrib(EglDisplay, conf[i], EGL10.EGL_RED_SIZE, value);
text+="EGL_RED_SIZE = "+value[0]+"\n";
Egl.eglGetConfigAttrib(EglDisplay, conf[i], EGL10.EGL_GREEN_SIZE, value);
text+="EGL_GREEN_SIZE = "+value[0]+"\n";
Egl.eglGetConfigAttrib(EglDisplay, conf[i], EGL10.EGL_BLUE_SIZE, value);
text+="EGL_BLUE_SIZE = "+value[0]+"\n";
Egl.eglGetConfigAttrib(EglDisplay, conf[i], EGL10.EGL_ALPHA_SIZE, value);
text+="EGL_ALPHA_SIZE = "+value[0]+"\n";
Egl.eglGetConfigAttrib(EglDisplay, conf[i], EGL10.EGL_DEPTH_SIZE, value);
text+="EGL_DEPTH_SIZE = "+value[0]+"\n";
Egl.eglGetConfigAttrib(EglDisplay, conf[i], EGL10.EGL_SAMPLE_BUFFERS, value);
text+="EGL_SAMPLE_BUFFERS = "+value[0]+"\n";
Egl.eglGetConfigAttrib(EglDisplay, conf[i], EGL10.EGL_SAMPLES, value);
text+="EGL_SAMPLES = "+value[0]+"\n";
Egl.eglGetConfigAttrib(EglDisplay, conf[i], EGL_COVERAGE_BUFFERS_NV, value);
text+="EGL_COVERAGE_BUFFERS_NV = "+value[0]+"\n";
Egl.eglGetConfigAttrib(EglDisplay, conf[i], EGL_COVERAGE_SAMPLES_NV, value);
text+="EGL_COVERAGE_SAMPLES_NV = "+value[0]+"\n\n";
} else { break; }
}
return text;
}
}
Про антиалайзинг ( сглаживание ):
Антиалайзинг включается на уровне драйвера.
Использование поверхностей с EGL_SAMPLE_BUFFERS задает режим MSAA
EGL_SAMPLES задает количество семплов на пиксель, например при 4 получаем режим MSAAx4
В процессе рендеринга управлять MSAA-сглаживанием нельзя.
EGL_COVERAGE_BUFFERS_NV и EGL_COVERAGE_SAMPLES_NV задают режим CSAA аналогичным образом.
Некоторые чипы, Tegra например, могут работать только с CSAA антиалайзингом.
В процессе рендеринга возможно управлять CSAA.
Но я бы не советовал использовать ни тот ни другой режим - а использовать
FXAA.Он намного "легче" в плане вычислений, просчитывается за один проход постобработки и дает лучший визуальный результат.
FXAA возможен только для OpenGL ES 2.0 и последующих редакций.
Остался один момент.
Вспомните строчку из GLSurfaceView:
...
holder.setFormat(PixelFormat.RGB_565);
...
Что будет если мы попытаемся инициализировать GLSurface в режиме RGBA8888 с на поверхности с PixelFormat.RGB_565?
Варианты:
1. система приведет RGBA8888 к RGB565 и мы получим 16битный цвет без прозрачности.
2. система будет нормально отображать RGBA8888, наплевав на RGB565.
3. Все с треском упадет.
На самом деле может случится любой из перечисленных вариантов, поэтому настоятельно рекомендуется привести SurfaceView к формату GLSurface.
Привести SurfaceView нужному формату можно приблизительно таким способом:
...
GLSurfaceView.getHolder().setFormat(PixelFormat.RGBA_8888);
...
И еще, никогда не нужно хомячить и пытаться задействовать максимальную конфигурацию так как за все приходится расплачиваться производительностью.
Вот и все про инициализацию OpenGL ES 2.0.
Далее детальное рассмотрение GLSurfaceView, его методов и субклассов.
Сообщение отредактировал usnavii - 17.01.13, 15:17