基于textureview编写opengl程序

ps这篇文章不仅可以更清楚的了解TextureView,还可以学到一点关于opengl的知识。

与SurfaceView相比,TextureView并没有创建一个单独的Surface用来绘制,这使得它可以像一般的View一样执行一些变换操作,设置透明度等。

另外,Textureview必须在硬件加速开启的窗口中。下面的例子演示了通过TextureView来创建一个opengl程序。

基于TextureView的程序,我们需要实现TextureView.SurfaceTextureListener这个接口,首先给出Activity的代码,在该类中,我们实现了此接口:

package com.fyj.test;
import java.util.concurrent.atomic.AtomicBoolean;
import android.app.Activity;
import android.graphics.SurfaceTexture;
import android.os.Bundle;
import android.view.TextureView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
public class TextureviewtestActivity extends Activity implements TextureView.SurfaceTextureListener{
    private TextureView mTextureView;
    private Thread mProducerThread = null;
    private GLRendererImpl mRenderer;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        mTextureView = new TextureView(this);
        mTextureView.setSurfaceTextureListener(this);
        mTextureView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);//隐藏虚拟按键,即navigator bar
        setContentView(mTextureView);
        mRenderer = new GLRendererImpl(this);
    }
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
            int height) {
        // TODO Auto-generated method stub
        mRenderer.setViewport(width, height);
        mProducerThread = new GLProducerThread(surface, mRenderer, new AtomicBoolean(true));
        mProducerThread.start();
    }
    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        // TODO Auto-generated method stub
        mProducerThread = null;
        return true;
    }
    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
            int height) {
        // TODO Auto-generated method stub
        mRenderer.resize(width, height);
    }
    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        // TODO Auto-generated method stub
    }
}

其中,在方法onSurfaceTextureAvailable中,我们开启一个OpenGL线程,在该线程中执行OpenGL的一些操作,该线程的定义如下:

package com.fyj.test;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
import android.graphics.SurfaceTexture;
import android.opengl.GLUtils;
public class GLProducerThread extends Thread {
    private AtomicBoolean mShouldRender;
    private SurfaceTexture mSurfaceTexture;
    private GLRenderer mRenderer;
    private EGL10 mEgl;
    private EGLDisplay mEglDisplay = EGL10.EGL_NO_DISPLAY;
    private EGLContext mEglContext = EGL10.EGL_NO_CONTEXT;
    private EGLSurface mEglSurface = EGL10.EGL_NO_SURFACE;
    private GL mGL;
    private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
    private static final int EGL_OPENGL_ES2_BIT = 4;
    public interface GLRenderer {
        public void drawFrame();
    }
    public GLProducerThread(SurfaceTexture surfaceTexture, GLRenderer renderer, AtomicBoolean shouldRender)
    {
        mSurfaceTexture = surfaceTexture;
        mRenderer = renderer;
        mShouldRender = shouldRender;
    }
    private void initGL()
    {
        mEgl = (EGL10)EGLContext.getEGL();
        mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
        if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
            throw new RuntimeException("eglGetdisplay failed : " +
                                                                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
        }
        int \[\]version = new int\[2\];
        if (!mEgl.eglInitialize(mEglDisplay, version)) {
            throw new RuntimeException("eglInitialize failed : " +
                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
        }
        int \[\]configAttribs = {
                EGL10.EGL_BUFFER_SIZE, 32,
                EGL10.EGL_ALPHA_SIZE, 8,
                EGL10.EGL_BLUE_SIZE, 8,
                EGL10.EGL_GREEN_SIZE, 8,
                EGL10.EGL_RED_SIZE, 8,
                EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
                EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
                EGL10.EGL_NONE
        };
        int \[\]numConfigs = new int\[1\];
        EGLConfig \[\]configs = new EGLConfig\[1\];
        if (!mEgl.eglChooseConfig(mEglDisplay, configAttribs, configs, 1, numConfigs)) {
            throw new RuntimeException("eglChooseConfig failed : " +
                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
        }
        int \[\]contextAttribs = {
                EGL_CONTEXT_CLIENT_VERSION, 2,
                EGL10.EGL_NONE
        };
        mEglContext = mEgl.eglCreateContext(mEglDisplay, configs\[0\], EGL10.EGL_NO_CONTEXT, contextAttribs);
        mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, configs\[0\], mSurfaceTexture, null);
        if (mEglSurface == EGL10.EGL_NO_SURFACE || mEglContext == EGL10.EGL_NO_CONTEXT) {
            int error = mEgl.eglGetError();
            if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
                throw new RuntimeException("eglCreateWindowSurface returned  EGL_BAD_NATIVE_WINDOW. " );
            }
            throw new RuntimeException("eglCreateWindowSurface failed : " +
                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
        }
        if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
            throw new RuntimeException("eglMakeCurrent failed : " +
                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
        }
        mGL = mEglContext.getGL();
    }
    private void destoryGL()
    {
        mEgl.eglDestroyContext(mEglDisplay, mEglContext);
        mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
        mEglContext = EGL10.EGL_NO_CONTEXT;
        mEglSurface = EGL10.EGL_NO_SURFACE;
    }
    public void run()
    {
        initGL();
        if (mRenderer != null) {
            ((GLRendererImpl)mRenderer).initGL();
        }
        while (mShouldRender != null && mShouldRender.get() != false) {
            if (mRenderer != null)
                mRenderer.drawFrame();
            mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
            try {
                sleep(5);
            } catch(InterruptedException e) {
            }
        }
        destoryGL();
    }
}

在该线程中,我们定义了GLRenderer接口,该接口的实现者负责具体的OpenGL绘制代码:

package com.fyj.test;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.Log;
import com.fyj.test.GLProducerThread.GLRenderer;
public class GLRendererImpl implements GLRenderer {
    private int mProgramObject;
    private int mWidth;
    private int mHeight;
    private FloatBuffer mVertices;
    private ShortBuffer mTexCoords;
    private Context mContext;
    private int mTexID;
    private static String TAG = "GLRendererImpl";
    private final float\[\] mVerticesData = { -0.5f, -0.5f, 0, 0.5f, -0.5f, 0, -0.5f, 0.5f, 0, 0.5f, 0.5f, 0 };
    private final short\[\] mTexCoordsData = {0, 1, 1, 1, 0, 0, 1, 0};
    public GLRendererImpl(Context ctx)
    {
        mVertices = ByteBuffer.allocateDirect(mVerticesData.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mVertices.put(mVerticesData).position(0);
        mTexCoords = ByteBuffer.allocateDirect(mTexCoordsData.length * 2)
                .order(ByteOrder.nativeOrder()).asShortBuffer();
        mTexCoords.put(mTexCoordsData).position(0);
        mContext = ctx;
    }
    public void setViewport(int width, int height)
    {
        mWidth = width;
        mHeight = height;
    }
    public void initGL()
    {
        comipleAndLinkProgram();
        loadTexture();
        GLES20.glClearColor(0,  0, 0, 0);
    }
    public void resize(int width, int height)
    {
        mWidth = width;
        mHeight = height;
    }
    @Override
    public void drawFrame() {
        // TODO Auto-generated method stub
        GLES20.glViewport(0, 0, mWidth, mHeight);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glUseProgram(mProgramObject);
        GLES20.glVertexAttribPointer(0, 3, GLES20.GL_FLOAT, false, 0, mVertices);
        GLES20.glEnableVertexAttribArray(0);
        GLES20.glVertexAttribPointer(1, 2, GLES20.GL_SHORT, false, 0, mTexCoords);
        GLES20.glEnableVertexAttribArray(1);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexID);
        int loc = GLES20.glGetUniformLocation(mProgramObject, "u_Texture");
        GLES20.glUniform1f(loc, 0);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        Log.i("GLRendererImpl", "drawing..." + mWidth);
    }
    private void loadTexture() {
        Bitmap b = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher);
        if (b != null) {
            int \[\]texID = new int\[1\];
            GLES20.glGenTextures(1, texID, 0);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texID\[0\]);
            mTexID = texID\[0\];
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
                    GLES20.GL_LINEAR);
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
                    GLES20.GL_LINEAR);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
                    GLES20.GL_REPEAT);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
                    GLES20.GL_REPEAT);
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, b, 0);
            b.recycle();
        }
    }
    private int loadShader(int shaderType, String shaderSource) {
        int shader;
        int\[\] compiled = new int\[1\];
        // Create the shader object
        shader = GLES20.glCreateShader(shaderType);
        if (shader == 0)
            return 0;
        // Load the shader source
        GLES20.glShaderSource(shader, shaderSource);
        // Compile the shader
        GLES20.glCompileShader(shader);
        // Check the compile status
        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
        if (compiled\[0\] == 0) {
            Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
            GLES20.glDeleteShader(shader);
            return 0;
        }
        return shader;
    }
    private void comipleAndLinkProgram() {
        String vShaderStr = "attribute vec4 a_position;    \\n"
                +"attribute vec2 a_texCoords; \\n"
                +"varying vec2 v_texCoords; \\n"
                + "void main()                  \\n"
                + "{                            \\n"
                + "   gl_Position = a_position;  \\n"
                +"    v_texCoords = a_texCoords; \\n"
                + "}                            \\n";
        String fShaderStr = "precision mediump float;                     \\n"
                +"uniform sampler2D u_Texture; \\n"
                +"varying vec2 v_texCoords; \\n"
                + "void main()                                  \\n"
                + "{                                            \\n"
                + "  gl_FragColor = texture2D(u_Texture, v_texCoords) ;\\n"
                + "}                                            \\n";
        int vertexShader;
        int fragmentShader;
        int programObject;
        int\[\] linked = new int\[1\];
        // Load the vertex/fragment shaders
        vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vShaderStr);
        fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fShaderStr);
        // Create the program object
        programObject = GLES20.glCreateProgram();
        if (programObject == 0)
            return ;
        GLES20.glAttachShader(programObject, vertexShader);
        GLES20.glAttachShader(programObject, fragmentShader);
        // Bind vPosition to attribute 0
        GLES20.glBindAttribLocation(programObject, 0, "a_position");
        GLES20.glBindAttribLocation(programObject, 1, "a_texCoords");
        // Link the program
        GLES20.glLinkProgram(programObject);
        // Check the link status
        GLES20.glGetProgramiv(programObject, GLES20.GL_LINK_STATUS, linked, 0);
        if (linked\[0\] == 0) {
            Log.e(TAG, "Error linking program:");
            Log.e(TAG, GLES20.glGetProgramInfoLog(programObject));
            GLES20.glDeleteProgram(programObject);
            return  ;
        }
        mProgramObject = programObject;
    }
}

需要注意的是,所有EGL及GL相关的操作必须置于GLThread线程中进行。