离屏渲染(Pbuffer)

来源:互联网 发布:社会学属于法学吗 知乎 编辑:程序博客网 时间:2024/05/21 15:38


http://www.itwendao.com/article/detail/468311.html


上一节我们只是把情丝斩断了,还是没偷偷摸摸的干点见不得人的事,这节我们就来吧!
首先,我们来看EGL创建EGLSurface有三个方法:eglCreateWindowSurface()、eglCreatePbufferSurface()和eglCreatePixmapSurface()。这三者有什么不同呢?

  • WindowSurface
    顾名思义WindowSurface是和窗口相关的,也就是在屏幕上的一块显示区的封装,渲染后即显示在界面上。
  • PbufferSurface
    在显存中开辟一个空间,将渲染后的数据(帧)存放在这里。
  • PixmapSurface
    以位图的形式存放在内存中,据说各平台的支持不是很好。

做离屏渲染我们可以选择PbufferSurface或者PixmapSurface,什么?你说FBO(Frame Buffer Object),这种高深的学问身为小菜鸡的我根本就不懂好吗(帧缓存对象FBO是对帧缓存的封装,性能要优于Pbuffer但并非可以完全替代Pbuffer)。
OpenGL操作的最终目标实际上是帧缓存(Frame Buffer)后面的各种表现形式则是EGL对Frame Buffer的封装,这么理解会不会好点?


代码整理

这节的知识相对简单,我们先把之前的代码整理一下:

  • 新建GLSurface类,对EGLSurface进行封装
  • 将GLRenderer类改为抽象类,继承于Thread
  • GLRenderer添加一个阻塞队列(消息队列),用于交互和解耦
  • GLRenderer添加一个Event内部类
  • GLRenderer中添加生命周期,将渲染之类的具体工作放到实现类中
  • 添加ShaderUtil类,将着色器创建的代码封装到这个类中

GLSurface.java

public class GLSurface {    public static final int TYPE_WINDOW_SURFACE  = 0;    public static final int TYPE_PBUFFER_SURFACE = 1;    public static final int TYPE_PIXMAP_SURFACE  = 2;    protected final int type;    protected Object surface; // 显示控件(支持SurfaceView、SurfaceHolder、Surface和SurfaceTexture)    protected EGLSurface eglSurface = EGL14.EGL_NO_SURFACE;    protected Viewport viewport = new Viewport();    public GLSurface(int width, int height) {        setViewport(0, 0, width, height);        surface = null;        type = TYPE_PBUFFER_SURFACE;    }    public GLSurface(Surface surface, int width, int height) {        this(surface,0,0,width,height);    }    public GLSurface(Surface surface, int x, int y, int width, int height) {        setViewport(x, y, width, height);        this.surface = surface;        type = TYPE_WINDOW_SURFACE;    }    public void setViewport(int x, int y, int width, int height){        viewport.x = x;        viewport.y = y;        viewport.width = width;        viewport.height = height;    }    public void setViewport(Viewport viewport){        this.viewport = viewport;    }    public Viewport getViewport(){        return viewport;    }    public static class Viewport{        public int x;        public int y;        public int width;        public int height;    }}

GLRenderer.java

public abstract class GLRenderer extends Thread {    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 ArrayBlockingQueue<Event> eventQueue;    private final List<GLSurface> outputSurfaces;    private boolean rendering;    private boolean isRelease;    public GLRenderer() {        setName("GLRenderer-" + getId());        outputSurfaces = new ArrayList<>();        rendering = false;        isRelease = false;        eventQueue = new ArrayBlockingQueue<>(100);    }    private boolean makeOutputSurface(GLSurface surface) {        // 创建Surface缓存        try {            switch (surface.type) {                case GLSurface.TYPE_WINDOW_SURFACE: {                    final int[] attributes = {EGL14.EGL_NONE};                    // 创建失败时返回EGL14.EGL_NO_SURFACE                    surface.eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface.surface, attributes, 0);                    break;                }                case GLSurface.TYPE_PBUFFER_SURFACE: {                    final int[] attributes = {                            EGL14.EGL_WIDTH, surface.viewport.width,                            EGL14.EGL_HEIGHT, surface.viewport.height,                            EGL14.EGL_NONE};                    // 创建失败时返回EGL14.EGL_NO_SURFACE                    surface.eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, attributes, 0);                    break;                }                case GLSurface.TYPE_PIXMAP_SURFACE: {                    Log.w(TAG, "nonsupport pixmap surface");                    return false;                }                default:                    Log.w(TAG, "surface type error " + surface.type);                    return false;            }        } catch (Exception e) {            Log.w(TAG, "can't create eglSurface");            surface.eglSurface = EGL_NO_SURFACE;            return false;        }        return true;    }    public void addSurface(@NonNull final GLSurface surface){        Event event = new Event(Event.ADD_SURFACE);        event.param = surface;        if(!eventQueue.offer(event))            Log.e(TAG,"queue full");    }    public void removeSurface(@NonNull final GLSurface surface){        Event event = new Event(Event.REMOVE_SURFACE);        event.param = surface;        if(!eventQueue.offer(event))            Log.e(TAG,"queue full");    }    /**     * 开始渲染     * 启动线程并等待初始化完毕     */    public void startRender(){        if(!eventQueue.offer(new Event(Event.START_RENDER)))            Log.e(TAG,"queue full");        if(getState()==State.NEW) {            super.start(); // 启动渲染线程        }    }    public void stopRender(){        if(!eventQueue.offer(new Event(Event.STOP_RENDER)))            Log.e(TAG,"queue full");    }    public boolean postRunnable(@NonNull Runnable runnable){        Event event = new Event(Event.RUNNABLE);        event.param = runnable;        if(!eventQueue.offer(event)) {            Log.e(TAG, "queue full");            return false;        }        return true;    }    @Override    public void start() {        Log.w(TAG,"Don't call this function");    }    public void requestRender(){        eventQueue.offer(new Event(Event.REQ_RENDER));    }    /**     * 创建OpenGL环境     */    private void createGL() {        // 获取显示设备(默认的显示设备)        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];        // 创建OpenGL上下文(可以先不设置EGLSurface,但EGLContext必须创建,        // 因为后面调用GLES方法基本都要依赖于EGLContext)        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());        }        // 设置默认的上下文环境和输出缓冲区(小米4上如果不设置有效的eglSurface后面创建着色器会失败,可以先创建一个默认的eglSurface)        //EGL14.eglMakeCurrent(eglDisplay, surface.eglSurface, surface.eglSurface, eglContext);        EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, eglContext);    }    /**     * 销毁OpenGL环境     */    private void destroyGL() {        EGL14.eglDestroyContext(eglDisplay, eglContext);        eglContext = EGL14.EGL_NO_CONTEXT;        eglDisplay = EGL14.EGL_NO_DISPLAY;    }    /**     * 渲染到各个eglSurface     */    private void render(){        // 渲染(绘制)        for(GLSurface output:outputSurfaces){            if(output.eglSurface== EGL_NO_SURFACE) {                if(!makeOutputSurface(output))                    continue;            }            // 设置当前的上下文环境和输出缓冲区            EGL14.eglMakeCurrent(eglDisplay, output.eglSurface, output.eglSurface, eglContext);            // 设置视窗大小及位置            GLES20.glViewport(output.viewport.x, output.viewport.y, output.viewport.width, output.viewport.height);            // 绘制            onDrawFrame(output);            // 交换显存(将surface显存和显示器的显存交换)            EGL14.eglSwapBuffers(eglDisplay, output.eglSurface);        }    }    @Override    public void run() {        Event event;        Log.d(TAG,getName()+": render create");        createGL();        onCreated();        // 渲染        while(!isRelease){            try {                event = eventQueue.take();                switch(event.event){                    case Event.ADD_SURFACE: {                        // 创建eglSurface                        GLSurface surface = (GLSurface)event.param;                        Log.d(TAG,"add:"+surface);                        makeOutputSurface(surface);                        outputSurfaces.add(surface);                        break;                    }                    case Event.REMOVE_SURFACE: {                        GLSurface surface = (GLSurface)event.param;                        Log.d(TAG,"remove:"+surface);                        EGL14.eglDestroySurface(eglDisplay, surface.eglSurface);                        outputSurfaces.remove(surface);                        break;                    }                    case Event.START_RENDER:                        rendering = true;                        break;                    case Event.REQ_RENDER: // 渲染                        if(rendering) {                            onUpdate();                            render(); // 如果surface缓存没有释放(被消费)那么这里将卡住                        }                        break;                    case Event.STOP_RENDER:                        rendering = false;                        break;                    case Event.RUNNABLE:                        ((Runnable)event.param).run();                        break;                    case Event.RELEASE:                        isRelease = true;                        break;                    default:                        Log.e(TAG,"event error: "+event);                        break;                }            } catch (InterruptedException e) {                e.printStackTrace();            }        }        // 回调        onDestroy();        // 销毁eglSurface        for(GLSurface outputSurface:outputSurfaces){            EGL14.eglDestroySurface(eglDisplay, outputSurface.eglSurface);            outputSurface.eglSurface = EGL_NO_SURFACE;        }        destroyGL();        eventQueue.clear();        Log.d(TAG,getName()+": render release");    }    /**     * 退出OpenGL渲染并释放资源     * 这里先将渲染器释放(renderer)再退出looper,因为renderer里面可能持有这个looper的handler,     * 先退出looper再释放renderer可能会报一些警告信息(sending message to a Handler on a dead thread)     */    public void release(){        if(eventQueue.offer(new Event(Event.RELEASE))){            // 等待线程结束,如果不等待,在快速开关的时候可能会导致资源竞争(如竞争摄像头)            // 但这样操作可能会引起界面卡顿,择优取舍            while (isAlive()){                try {                    this.join(1000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }    /**     * 当创建完基本的OpenGL环境后调用此方法,可以在这里初始化纹理之类的东西     */    public abstract void onCreated();    /**     * 在渲染之前调用,用于更新纹理数据。渲染一帧调用一次     */    public abstract void onUpdate();    /**     * 绘制渲染,每次绘制都会调用,一帧数据可能调用多次(不同是输出缓存)     * @param outputSurface 输出缓存位置surface     */    public abstract void onDrawFrame(GLSurface outputSurface);    /**     * 当渲染器销毁前调用,用户回收释放资源     */    public abstract void onDestroy();    private static String getEGLErrorString() {        return GLUtils.getEGLErrorString(EGL14.eglGetError());    }    private static class Event {        static final int ADD_SURFACE = 1; // 添加输出的surface        static final int REMOVE_SURFACE = 2; // 移除输出的surface        static final int START_RENDER = 3; // 开始渲染        static final int REQ_RENDER = 4; // 请求渲染        static final int STOP_RENDER = 5; // 结束渲染        static final int RUNNABLE = 6; //        static final int RELEASE = 7; // 释放渲染器        final int event;        Object param;        Event(int event) {            this.event = event;        }    }}

ShaderUtil.java

public class ShaderUtil {    /**     * 加载制定shader的方法     * @param shaderType shader的类型  GLES20.GL_VERTEX_SHADER   GLES20.GL_FRAGMENT_SHADER     * @param source shader的脚本字符串     * @return 着色器id     */    private static int loadShader(int shaderType,String source) {        // 创建一个新shader        int shader = GLES20.glCreateShader(shaderType);        // 若创建成功则加载shader        if (shader != 0) {            //加载shader的源代码            GLES20.glShaderSource(shader, source);            //编译shader            GLES20.glCompileShader(shader);            //存放编译成功shader数量的数组            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;    }    /**     * 创建shader程序的方法     */    public static 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;        }        //创建程序        int program = GLES20.glCreateProgram();        //若程序创建成功则向程序中加入顶点着色器与片元着色器        if (program != 0) {            //向程序中加入顶点着色器            GLES20.glAttachShader(program, vertexShader);            checkGlError("glAttachShader");            //向程序中加入片元着色器            GLES20.glAttachShader(program, pixelShader);            checkGlError("glAttachShader");            //链接程序            GLES20.glLinkProgram(program);            //存放链接成功program数量的数组            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;    }    //检查每一步操作是否有错误的方法    public static void checkGlError(String op) {        int error;        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {            Log.e("ES20_ERROR", op + ": glError " + error);            throw new RuntimeException(op + ": glError " + error);        }    }    //从sh脚本中加载shader内容的方法    public static String loadFromAssetsFile(String fname, Resources r) {        String result = null;        try {            InputStream in = r.getAssets().open(fname);            int ch = 0;            ByteArrayOutputStream baos = new ByteArrayOutputStream();            while ((ch = in.read()) != -1) {                baos.write(ch);            }            byte[] buff = baos.toByteArray();            baos.close();            in.close();            result = new String(buff, "UTF-8");            result = result.replaceAll("\\r\\n", "\n");        } catch (Exception e) {            e.printStackTrace();        }        return result;    }}

离屏渲染关键部分就是在makeOutputSurface()方法中的GLSurface.TYPE_PBUFFER_SURFACE这个case里面,仅9行代码,简单吧(指定大小,使用EGL14.eglCreatePbufferSurface()创建)

测试

工程整理完毕,添加一些测试代码

  • 新建TestRenderer继承于GLRenderer,在onCreated()中创建着色器和顶点,在onDrawFrame()中进行绘制

TestRenderer.java

public class TestRenderer extends GLRenderer {    private static final String TAG = "TestRenderer";    private int program;    private int vPosition;    private int uColor;    private FloatBuffer vertices;    /**     * 获取图形的顶点     * 特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer     * 转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题     *     * @return 顶点Buffer     */    private FloatBuffer getVertices() {        float vertices[] = {                0.0f, 0.5f,                -0.5f, -0.5f,                0.5f, -0.5f,        };        // 创建顶点坐标数据缓冲        // vertices.length*4是因为一个float占四个字节        ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);        vbb.order(ByteOrder.nativeOrder());             //设置字节顺序        FloatBuffer vertexBuf = vbb.asFloatBuffer();    //转换为Float型缓冲        vertexBuf.put(vertices);                        //向缓冲区中放入顶点坐标数据        vertexBuf.position(0);                          //设置缓冲区起始位置        return vertexBuf;    }    @Override    public void onCreated() {        //基于顶点着色器与片元着色器创建程序        program = createProgram(verticesShader, fragmentShader);        // 获取着色器中的属性引用id(传入的字符串就是我们着色器脚本中的属性名)        vPosition = GLES20.glGetAttribLocation(program, "vPosition");        uColor = GLES20.glGetUniformLocation(program, "uColor");        vertices = getVertices();    }    @Override    public void onUpdate() {    }    @Override    public void onDrawFrame(GLSurface surface) {        // 设置clear color颜色RGBA(这里仅仅是设置清屏时GLES20.glClear()用的颜色值而不是执行清屏)        GLES20.glClearColor(1.0f, 0, 0, 1.0f);        // 清除深度缓冲与颜色缓冲(清屏,否则会出现绘制之外的区域花屏)        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);        // 使用某套shader程序        GLES20.glUseProgram(program);        // 为画笔指定顶点位置数据(vPosition)        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, vertices);        // 允许顶点位置数据数组        GLES20.glEnableVertexAttribArray(vPosition);        // 设置属性uColor(颜色 索引,R,G,B,A)        GLES20.glUniform4f(uColor, 0.0f, 1.0f, 0.0f, 1.0f);        // 绘制        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3);    }    @Override    public void onDestroy() {    }    // 顶点着色器的脚本    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" // 给此片元的填充色            + "}";}
  • 在activity_main.xml添加一个ImageView用于显示渲染结果

activity_main.xml

<?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">    <ImageView        android:id="@+id/iv_main_image"        android:layout_width="50dp"        android:layout_height="50dp"        android:layout_gravity="center"        android:contentDescription="@string/app_name" />    <SurfaceView        android:id="@+id/sv_main_demo"        android:layout_width="match_parent"        android:layout_height="match_parent" /></LinearLayout>
  • 修改MainActivity
    在onCreate()中创建一个PbufferSurface,加入渲染器,启动渲染,取回像素数据(要在OpenGL线程)转换成Bitmap设置给ImageView

MainActivity.java

public class MainActivity extends Activity {    private TestRenderer glRenderer;    private ImageView imageIv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        SurfaceView sv = (SurfaceView)findViewById(R.id.sv_main_demo);        imageIv = (ImageView)findViewById(R.id.iv_main_image);        glRenderer = new TestRenderer();        GLSurface glPbufferSurface = new GLSurface(512,512);        glRenderer.addSurface(glPbufferSurface);        glRenderer.startRender();        glRenderer.requestRender();        glRenderer.postRunnable(new Runnable() {            @Override            public void run() {                IntBuffer ib = IntBuffer.allocate(512 * 512);                GLES20.glReadPixels(0, 0, 512, 512, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);                final Bitmap bitmap = frameToBitmap(512, 512, ib);                new Handler(Looper.getMainLooper()).post(new Runnable() {                    @Override                    public void run() {                        imageIv.setImageBitmap(bitmap);                    }                });            }        });        sv.getHolder().addCallback(new SurfaceHolder.Callback() {            @Override            public void surfaceCreated(SurfaceHolder surfaceHolder) {            }            @Override            public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {                GLSurface glWindowSurface = new GLSurface(surfaceHolder.getSurface(),width,height);                glRenderer.addSurface(glWindowSurface);                glRenderer.requestRender();            }            @Override            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {            }        });    }    @Override    protected void onDestroy() {        glRenderer.release();        glRenderer = null;        super.onDestroy();    }    /**     * 将数据转换成bitmap(OpenGL和Android的Bitmap色彩空间不一致,这里需要做转换)     *     * @param width 图像宽度     * @param height 图像高度     * @param ib 图像数据     * @return bitmap     */    private static Bitmap frameToBitmap(int width, int height, IntBuffer ib) {        int pixs[] = ib.array();        // 扫描转置(OpenGl:左上->右下 Bitmap:左下->右上)        for (int y = 0; y < height / 2; y++) {            for (int x = 0; x < width; x++) {                int pos1 = y * width + x;                int pos2 = (height - 1 - y) * width + x;                int tmp = pixs[pos1];                pixs[pos1] = (pixs[pos2] & 0xFF00FF00) | ((pixs[pos2] >> 16) & 0xff) | ((pixs[pos2] << 16) & 0x00ff0000); // ABGR->ARGB                pixs[pos2] = (tmp & 0xFF00FF00) | ((tmp >> 16) & 0xff) | ((tmp << 16) & 0x00ff0000);            }        }        if (height % 2 == 1) { // 中间一行            for (int x = 0; x < width; x++) {                int pos = (height / 2 + 1) * width + x;                pixs[pos] = (pixs[pos] & 0xFF00FF00) | ((pixs[pos] >> 16) & 0xff) | ((pixs[pos] << 16) & 0x00ff0000);            }        }        return Bitmap.createBitmap(pixs, width, height, Bitmap.Config.ARGB_8888);    }}

运行一下,这里我们就能看到两个三角形了,顶部的小三角形就是在后台渲染的图像了


源码

点鸡下崽


原创粉丝点击