Android OpenGLES3绘图:EGL无屏绘制

一般渲染绘图都是在GLSurfaceView上,查看源码可知道,GLSurfaceView就是继承自SurfaceView,并且自己管理了OpenGL环境。OpenGL绘制调用的都是静态方法,所以OpenGL环境默认是跟创建它的线程绑定,然后在这个线程调用OpenGL的方法才有效果。

在某些情况下,需要脱离GLSurfaceView使用OpenGL绘制,比如WebRTC视频添加滤镜,它需要采集视频然后用OpenGL处理,处理后再展示到本地并且编码传输。这时就需要自己创建OpenGL环境,主要是创建EGL配置和线程。

1.创建EGL配置

先创建EGLDisplay、EGLContext、EGLConfig这三个对象,创建同时检查是否创建成功。


    public EglCore() {
        mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
        if (mEglDisplay == EGL14.EGL_NO_DISPLAY) {
            throw new RuntimeException("unable to get EGL14 display");
        }
        int[] version = new int[2];
        if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 0)) {
            mEglDisplay = null;
            throw new RuntimeException("unable to initialize EGL14");
        }

        String vendor = EGL14.eglQueryString(mEglDisplay, EGL14.EGL_VENDOR);
        Log.d("chao", "egl vendor: " + vendor); // 打印此版本EGL的实现厂商

        String versionStr = EGL14.eglQueryString(mEglDisplay, EGL14.EGL_VERSION);
        Log.d("chao", "egl version: " + versionStr);// 打印EGL版本号

        String extension = EGL14.eglQueryString(mEglDisplay, EGL14.EGL_EXTENSIONS);
        Log.d("chao", "egl extension: " + extension); //打印支持的EGL扩展


        int[] attributes = new int[] {
                EGL14.EGL_RED_SIZE, 8,  //指定RGB中的R大小(bits)
                EGL14.EGL_GREEN_SIZE, 8, //指定G大小
                EGL14.EGL_BLUE_SIZE, 8,  //指定B大小
                EGL14.EGL_ALPHA_SIZE, 8, //指定Alpha大小,以上四项实际上指定了像素格式
                EGL14.EGL_DEPTH_SIZE, 16, //指定深度缓存(Z Buffer)大小
                EGL14.EGL_RENDERABLE_TYPE, 4, //指定渲染api类别,这里或者是硬编码的4,或者是EGL14.EGL_OPENGL_ES2_BIT
                EGL14.EGL_NONE };  //总是以EGL10.EGL_NONE结尾

        EGLConfig[] configs = new EGLConfig[1];
        int[] numConfigs = new int[1];
        if (!EGL14.eglChooseConfig(mEglDisplay, attributes, 0, configs, 0, configs.length, numConfigs, 0)) {    //获取所有
            Log.w("chao", "unable to find RGB8888 suitable EGLConfig");
        }
        mEglConfig = configs[0];

        int[] attrs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE };
        mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig, EGL14.EGL_NO_CONTEXT, attrs, 0);
    }

然后创建EGLSurface,并且绑定到当前环境。


    EGLSurface createPbufferSurface(int w, int h) {
        int [] attrs = {
                EGL14.EGL_WIDTH, w,
                EGL14.EGL_HEIGHT, h,
                EGL14.EGL_NONE
        };
        EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attrs, 0);
        if (eglSurface == null) {
            throw new RuntimeException("eglCreatePbufferSurface failed!");
        }
        return eglSurface;
    }

    public void makeCurrent(int w, int h) {
        mEglSurface = createPbufferSurface(w, h);
        if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
            Log.e("chao", "eglMakeCurrent failed");
        }
    }

2.创建EGL线程

其实就是创建一个HandlerThread,在这个线程里调用上面的方法初始化EGL,然后不停地调用着色器的绘制方法。


public class EglThread {
    Handler mHandler;
    EglCore mEglCore;

    public EglThread(String name) {
        HandlerThread ht = new HandlerThread(name);
        ht.start();

        mHandler = new Handler(ht.getLooper());
    }

    public void initEglCore(int w, int h) {
        if (mEglCore == null) {
            mHandler.post(() -> {
                mEglCore = new EglCore();
                mEglCore.makeCurrent(w, h);
            });
        }
    }

    public void post(Runnable r) {
        if (mHandler != null) {
            mHandler.post(r);
        }
    }


    public void destroy() {
        if (mHandler != null) {
            mHandler.post(() -> {
                mEglCore.release();
                mHandler.getLooper().quitSafely();
            });
        }
    }
}

3.使用EGL绘制

实际绘制一下来检验效果,找一个之前使用的SimpleRender,它是一个简单的OpenGL绘制图片的着色器,图片可以不停转动。

在eglThread里面调用
simpleRender.onSurfaceCreated(null, null);
simpleRender.onSurfaceChanged(null, w, h);
这俩方法初始化着色器,然后在while循环里反复调用
simpleRender.onDrawFrame(null);
就可以不停绘制了。

GLES20.glReadPixels方法可以从OpenGL里面获取绘制后的结果,转换成Bitmap就可以展示到ImageView上面。

public class EglActivity extends AppCompatActivity {

    ImageView imageView;

    EglThread eglThread;

    SimpleRender simpleRender;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        imageView = new ImageView(this);
        imageView.setLayoutParams(new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        setContentView(imageView);

        imageView.setBackgroundColor(Color.BLUE);

        simpleRender = new SimpleRender();

        eglThread = new EglThread("chao-gl-thread");

        imageView.post(() -> {
            int w = imageView.getWidth();
            int h = imageView.getHeight();
            eglThread.initEglCore(w, h);
            eglThread.post(() -> {
                simpleRender.onSurfaceCreated(null, null);
                simpleRender.onSurfaceChanged(null, w, h);
                while(!EglActivity.this.isFinishing() && !EglActivity.this.isDestroyed()) {
                    simpleRender.onDrawFrame(null);
                    Bitmap bitmap = getBitmapFromGL(w, h);
                    imageView.post(() -> {
                        imageView.setImageBitmap(bitmap);
                    });

                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        });
    }

    Bitmap getBitmapFromGL(int w, int h) {
        IntBuffer intBuffer =  IntBuffer.allocate(w * h);
        GLES20.glReadPixels(0, 0, w, h, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, intBuffer);
        Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        bitmap.copyPixelsFromBuffer(intBuffer);
        return bitmap;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        eglThread.destroy();
    }
}

虽然用的是ImageView,但是实现效果跟用GLSurfaceView完全相同。

4 Github地址

完整项目在SurfacePaint项目下的opengles3模块里。

posted @   rome753  阅读(281)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
点击右上角即可分享
微信分享提示