Android OpenGL ES從白癡到入門(三):引入EGL

啰嗦

上一節(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渲染一般流程

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

源碼

點雞下崽

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市遍搞,隨后出現(xiàn)的幾起案子罗侯,更是在濱河造成了極大的恐慌,老刑警劉巖溪猿,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件硬鞍,死亡現(xiàn)場離奇詭異扛伍,居然都是意外死亡,警方通過查閱死者的電腦和手機完疫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門每瞒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來艺普,“玉大人华畏,你說我怎么就攤上這事谨敛。” “怎么了抗悍?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長钳枕。 經(jīng)常有香客問我缴渊,道長,這世上最難降的妖魔是什么鱼炒? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任衔沼,我火速辦了婚禮,結(jié)果婚禮上昔瞧,老公的妹妹穿的比我還像新娘指蚁。我一直安慰自己,他們只是感情好自晰,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布凝化。 她就那樣靜靜地躺著,像睡著了一般酬荞。 火紅的嫁衣襯著肌膚如雪搓劫。 梳的紋絲不亂的頭發(fā)上瞧哟,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音枪向,去河邊找鬼勤揩。 笑死,一個胖子當(dāng)著我的面吹牛秘蛔,可吹牛的內(nèi)容都是我干的陨亡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼深员,長吁一口氣:“原來是場噩夢啊……” “哼负蠕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起辨液,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤虐急,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后滔迈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體止吁,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年燎悍,在試婚紗的時候發(fā)現(xiàn)自己被綠了敬惦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡谈山,死狀恐怖俄删,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奏路,我是刑警寧澤畴椰,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站鸽粉,受9級特大地震影響斜脂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜触机,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一帚戳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧儡首,春花似錦片任、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至笔宿,卻和暖如春犁钟,著一層夾襖步出監(jiān)牢的瞬間棱诱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工涝动, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留迈勋,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓醋粟,卻偏偏與公主長得像靡菇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子米愿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容