啰嗦
上一節(jié)我們已經(jīng)創(chuàng)建了一個基于Android的OpenGL App惯吕,但沒有涉及到EGL,原因是GLSurfaceView已經(jīng)包含了這一塊怕午,本節(jié)將移除GLSurfaceView用SurfaceView來做預(yù)覽混埠。
也許你會問,既然Android已經(jīng)有幫我們處理的為何要多此一舉呢诗轻?原因是GLSurfaceView將OpenGL綁定到一起钳宪,也就是說GLSurfaceView一但銷毀,伴隨的OpenGL也一起銷毀了扳炬,一個OpenGL只能渲染一個GLSurfaceView吏颖。這不就是同生共死的唯一愛情么,這要一個花花公子如何接受得了呢恨樟!所以讓我們來揮淚斬情絲搞些小姨太吧半醉!(如果你的應(yīng)用是基于實時顯示,用不到保留狀態(tài)或者后臺渲染那這部分是不需要的)
EGL要做什么劝术?
EGL既然做平臺和OpenGL ES的中間件那EGL做的就肯定是和平臺息息相關(guān)的事:
- 創(chuàng)建繪圖窗口
也就是所謂的FrameBuffer缩多,F(xiàn)rameBuffer可以顯示到屏幕上(SurfaceView) - 創(chuàng)建渲染環(huán)境(Context上下文)
渲染環(huán)境指OpenGL ES的所有項目運行需要的數(shù)據(jù)結(jié)構(gòu)。如頂點养晋、片段著色器衬吆、頂點數(shù)據(jù)矩陣。
動手開始揮淚斬情絲
OpenGL的渲染是基于線程的绳泉,我們這里先創(chuàng)建一個GLRenderer類繼承于HandlerThread:
public class GLRenderer extends HandlerThread{
private static final String TAG = "GLThread";
private EGLConfig eglConfig = null;
private EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY;
private EGLContext eglContext = EGL14.EGL_NO_CONTEXT;
private int program;
private int vPosition;
private int uColor;
public GLRenderer() {
super("GLRenderer");
}
/**
* 創(chuàng)建OpenGL環(huán)境
*/
private void createGL(){
// 獲取顯示設(shè)備(默認的顯示設(shè)備)
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
// 初始化
int []version = new int[2];
if (!EGL14.eglInitialize(eglDisplay, version,0,version,1)) {
throw new RuntimeException("EGL error "+EGL14.eglGetError());
}
// 獲取FrameBuffer格式和能力
int []configAttribs = {
EGL14.EGL_BUFFER_SIZE, 32,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT,
EGL14.EGL_NONE
};
int []numConfigs = new int[1];
EGLConfig[]configs = new EGLConfig[1];
if (!EGL14.eglChooseConfig(eglDisplay, configAttribs,0, configs, 0,configs.length, numConfigs,0)) {
throw new RuntimeException("EGL error "+EGL14.eglGetError());
}
eglConfig = configs[0];
// 創(chuàng)建OpenGL上下文
int []contextAttribs = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT, contextAttribs,0);
if(eglContext== EGL14.EGL_NO_CONTEXT) {
throw new RuntimeException("EGL error "+EGL14.eglGetError());
}
}
/**
* 銷毀OpenGL環(huán)境
*/
private void destroyGL(){
EGL14.eglDestroyContext(eglDisplay, eglContext);
eglContext = EGL14.EGL_NO_CONTEXT;
eglDisplay = EGL14.EGL_NO_DISPLAY;
}
@Override
public synchronized void start() {
super.start();
new Handler(getLooper()).post(new Runnable() {
@Override
public void run() {
createGL();
}
});
}
public void release(){
new Handler(getLooper()).post(new Runnable() {
@Override
public void run() {
destroyGL()逊抡;
quit();
}
});
}
/**
* 加載制定shader的方法
* @param shaderType shader的類型 GLES20.GL_VERTEX_SHADER GLES20.GL_FRAGMENT_SHADER
* @param sourceCode shader的腳本
* @return shader索引
*/
private int loadShader(int shaderType,String sourceCode) {
// 創(chuàng)建一個新shader
int shader = GLES20.glCreateShader(shaderType);
// 若創(chuàng)建成功則加載shader
if (shader != 0) {
// 加載shader的源代碼
GLES20.glShaderSource(shader, sourceCode);
// 編譯shader
GLES20.glCompileShader(shader);
// 存放編譯成功shader數(shù)量的數(shù)組
int[] compiled = new int[1];
// 獲取Shader的編譯情況
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {//若編譯失敗則顯示錯誤日志并刪除此shader
Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
/**
* 創(chuàng)建shader程序的方法
*/
private int createProgram(String vertexSource, String fragmentSource) {
//加載頂點著色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
// 加載片元著色器
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
// 創(chuàng)建程序
int program = GLES20.glCreateProgram();
// 若程序創(chuàng)建成功則向程序中加入頂點著色器與片元著色器
if (program != 0) {
// 向程序中加入頂點著色器
GLES20.glAttachShader(program, vertexShader);
// 向程序中加入片元著色器
GLES20.glAttachShader(program, pixelShader);
// 鏈接程序
GLES20.glLinkProgram(program);
// 存放鏈接成功program數(shù)量的數(shù)組
int[] linkStatus = new int[1];
// 獲取program的鏈接情況
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
// 若鏈接失敗則報錯并刪除程序
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e("ES20_ERROR", "Could not link program: ");
Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
}
return program;
}
/**
* 獲取圖形的頂點
* 特別提示:由于不同平臺字節(jié)順序不同數(shù)據(jù)單元不是字節(jié)的一定要經(jīng)過ByteBuffer
* 轉(zhuǎn)換,關(guān)鍵是要通過ByteOrder設(shè)置nativeOrder()零酪,否則有可能會出問題
*
* @return 頂點Buffer
*/
private FloatBuffer getVertices() {
float vertices[] = {
0.0f, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f,
};
// 創(chuàng)建頂點坐標(biāo)數(shù)據(jù)緩沖
// vertices.length*4是因為一個float占四個字節(jié)
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder()); //設(shè)置字節(jié)順序
FloatBuffer vertexBuf = vbb.asFloatBuffer(); //轉(zhuǎn)換為Float型緩沖
vertexBuf.put(vertices); //向緩沖區(qū)中放入頂點坐標(biāo)數(shù)據(jù)
vertexBuf.position(0); //設(shè)置緩沖區(qū)起始位置
return vertexBuf;
}
public void render(Surface surface, int width, int height){
final int[] surfaceAttribs = { EGL14.EGL_NONE };
EGLSurface eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface, surfaceAttribs, 0);
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
// 初始化著色器
// 基于頂點著色器與片元著色器創(chuàng)建程序
program = createProgram(verticesShader, fragmentShader);
// 獲取著色器中的屬性引用id(傳入的字符串就是我們著色器腳本中的屬性名)
vPosition = GLES20.glGetAttribLocation(program, "vPosition");
uColor = GLES20.glGetUniformLocation(program, "uColor");
// 設(shè)置clear color顏色RGBA(這里僅僅是設(shè)置清屏?xí)rGLES20.glClear()用的顏色值而不是執(zhí)行清屏)
GLES20.glClearColor(1.0f, 0, 0, 1.0f);
// 設(shè)置繪圖的窗口(可以理解成在畫布上劃出一塊區(qū)域來畫圖)
GLES20.glViewport(0,0,width,height);
// 獲取圖形的頂點坐標(biāo)
FloatBuffer vertices = getVertices();
// 清屏
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
// 使用某套shader程序
GLES20.glUseProgram(program);
// 為畫筆指定頂點位置數(shù)據(jù)(vPosition)
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, vertices);
// 允許頂點位置數(shù)據(jù)數(shù)組
GLES20.glEnableVertexAttribArray(vPosition);
// 設(shè)置屬性uColor(顏色 索引,R,G,B,A)
GLES20.glUniform4f(uColor, 0.0f, 1.0f, 0.0f, 1.0f);
// 繪制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3);
// 交換顯存(將surface顯存和顯示器的顯存交換)
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
EGL14.eglDestroySurface(eglDisplay, eglSurface);
}
// 頂點著色器的腳本
private static final String verticesShader
= "attribute vec2 vPosition; \n" // 頂點位置屬性vPosition
+ "void main(){ \n"
+ " gl_Position = vec4(vPosition,0,1);\n" // 確定頂點位置
+ "}";
// 片元著色器的腳本
private static final String fragmentShader
= "precision mediump float; \n" // 聲明float類型的精度為中等(精度越高越耗資源)
+ "uniform vec4 uColor; \n" // uniform的屬性uColor
+ "void main(){ \n"
+ " gl_FragColor = uColor; \n" // 給此片元的填充色
+ "}";
}
這個類也簡單冒嫡,主要是將上一節(jié)的MyRenderer拷貝過來,加上EGL部分的代碼即可四苇。
createGL()方法獲取了一個默認的顯示設(shè)備(也就是手機屏幕)孝凌,初始化并返回當(dāng)前系統(tǒng)使用的OpenGL版本(主板本+子版本),然后通過配置(主要以鍵值對的方式配置月腋,最后由EGL_NONE結(jié)尾)得到一個EGLConfig蟀架,最后創(chuàng)建一個EGLContext。
destroyGL()方法則是釋放掉OpenGL的資源(主要就是EGLContext)罗售。
render()方法中主要是渲染辜窑,這里為了方便把渲染的環(huán)境和渲染寫在一起并只渲染一次(我們只畫了一個三角形),前三行代碼我們創(chuàng)建了一個EGLSurface并設(shè)置為當(dāng)前的渲染對象寨躁,后面eglSwapBuffers()交換了顯示器和EGLSurface的顯存穆碎,也就是將我們渲染的東西放到顯示器去顯示,這樣我們就看到我們繪制的三角形了职恳,最后就是銷毀我們創(chuàng)建的EGLSurface所禀,中間部分都是上一節(jié)拷貝過來的代碼方面。
這里要注意,OpenGL是基于線程的色徘,雖然有些方法可以在別的線程調(diào)用恭金,但最好還是都放到OpenGL所在的線程調(diào)用,否則可能調(diào)用后無效果(不一定報錯)
修改activity_main.xml褂策,把GLSurfaceView替換為SurfaceView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<SurfaceView
android:id="@+id/sv_main_demo"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
MainActivity.java也做相應(yīng)的修改横腿,實例化GLRenderer對象并啟動線程,在SurfaceView創(chuàng)建之后渲染一次:
public class MainActivity extends Activity {
private GLRenderer glRenderer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SurfaceView sv = (SurfaceView)findViewById(R.id.sv_main_demo);
glRenderer = new GLRenderer();
glRenderer.start();
sv.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
glRenderer.render(surfaceHolder.getSurface(),width,height); // 這里偷懶直接在主線程渲染了斤寂,大家切莫效仿
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
}
});
}
@Override
protected void onDestroy() {
glRenderer.release();
glRenderer = null;
super.onDestroy();
}
}
運行App耿焊,我們就得到了一個和上一節(jié)一樣的一個三角形:
EGLConfig 屬性
屬性 | 描述 | 默認值 |
---|---|---|
EGL_BUFFER_SIZE | 顏色緩沖區(qū)中所有組成顏色的位數(shù) | 0 |
EGL_RED_SIZE | 顏色緩沖區(qū)中紅色位數(shù) | 0 |
EGL_GREEN_SIZE | 顏色緩沖區(qū)中綠色位數(shù) | 0 |
EGL_BLUE_SIZE | 顏色緩沖區(qū)中藍色位數(shù) | 0 |
EGL_LUMINANCE_SIZE | 顏色緩沖區(qū)中亮度位數(shù) | 0 |
EGL_ALPHA_SIZE | 顏色緩沖區(qū)中透明度位數(shù) | 0 |
EGL_ALPHA_MASK_SIZE | 遮擋緩沖區(qū)透明度掩碼位數(shù) | 0 |
EGL_BIND_TO_TEXTURE_RGB | 綁定到 RGB 貼圖使能為真 | EGL_DONT_CARE |
EGL_BIND_TO_TEXTURE_RGBA | 綁定到 RGBA 貼圖使能為真 | EGL_DONT_CARE |
EGL_COLOR_BUFFER_TYPE | 顏色緩沖區(qū)類型 EGL_RGB_BUFFER, 或者EGL_LUMINANCE_BUFFER | EGL_RGB_BUFFER |
EGL_CONFIG_CAVEAT | 配置有關(guān)的警告信息 | EGL_DONT_CARE |
EGL_CONFIG_ID | 唯一的 EGLConfig 標(biāo)示值 | EGL_DONT_CARE |
EGL_CONFORMANT | 使用EGLConfig 創(chuàng)建的上下文符合要求時為真 | — |
EGL_DEPTH_SIZE | 深度緩沖區(qū)位數(shù) | 0 |
EGL_LEVEL | 幀緩沖區(qū)水平 | 0 |
EGL_MAX_PBUFFER_WIDTH | 使用EGLConfig 創(chuàng)建的PBuffer的最大寬度 | — |
EGL_MAX_PBUFFER_HEIGHT | 使用EGLConfig 創(chuàng)建的PBuffer最大高度 | — |
EGL_MAX_PBUFFER_PIXELS | 使用EGLConfig 創(chuàng)建的PBuffer最大尺寸 | — |
EGL_MAX_SWAP_INTERVAL | 最大緩沖區(qū)交換間隔 | EGL_DONT_CARE |
EGL_MIN_SWAP_INTERVAL | 最小緩沖區(qū)交換間隔 | EGL_DONT_CARE |
EGL_NATIVE_RENDERABLE | 如果操作系統(tǒng)渲染庫能夠使用EGLConfig 創(chuàng)建渲染渲染窗口 | EGL_DONT_CARE |
EGL_NATIVE_VISUAL_ID | 與操作系統(tǒng)通訊的可視ID句柄 | EGL_DONT_CARE |
EGL_NATIVE_VISUAL_TYPE | 與操作系統(tǒng)通訊的可視ID類型 | EGL_DONT_CARE |
EGL_RENDERABLE_TYPE | 渲染窗口支持的布局組成標(biāo)示符的遮擋位EGL_OPENGL_ES_BIT, EGL_OPENGL_ES2_BIT, orEGL_OPENVG_BIT that | EGL_OPENGL_ES_BIT |
EGL_SAMPLE_BUFFERS | 可用的多重采樣緩沖區(qū)位數(shù) | 0 |
EGL_SAMPLES | 每像素多重采樣數(shù) | 0 |
EGL_S TENCIL_SIZE | 模板緩沖區(qū)位數(shù) | 0 |
EGL_SURFACE_TYPE | EGL 窗口支持的類型EGL_WINDOW_BIT, EGL_PIXMAP_BIT,或EGL_PBUFFER_BIT | EGL_WINDOW_BIT |
EGL_TRANSPARENT_TYPE | 支持的透明度類型 | EGL_NONE |
EGL_TRANSPARENT_RED_VALUE | 透明度的紅色解釋 | EGL_DONT_CARE |
EGL_TRANSPARENT_GRE EN_VALUE | 透明度的綠色解釋 | EGL_DONT_CARE |
EGL_TRANSPARENT_BLUE_VALUE | 透明度的蘭色解釋 | EGL_DONT_CARE |