离屏渲染(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); }}
运行一下,这里我们就能看到两个三角形了,顶部的小三角形就是在后台渲染的图像了
源码
点鸡下崽
阅读全文
0 0
- 离屏渲染(Pbuffer)
- OpenGLes2.0 什么是Pbuffer
- OpenGL离屏渲染
- cocos2dx 离屏渲染
- 离屏渲染
- iOS 【离屏渲染】
- opengl离屏渲染
- 离屏渲染
- iOS 离屏渲染
- pbuffer 和fbo的差异
- opengl离屏渲染图片
- 离屏渲染学习笔记
- 离屏渲染学习笔记
- FBO离屏渲染技术
- iOS之离屏渲染
- iOS离屏渲染优化
- iOS 离屏渲染研究
- 离屏渲染学习笔记
- eclipse+maven
- oj 先序生成树以及print()queue
- JZOJ 5068. 【GDSOI2017第二轮模拟】树
- v$session & v$process各字段的说明
- Winpcap 开发教程
- 离屏渲染(Pbuffer)
- 阿里云的混合云战略,凭啥扯上Zstack?
- 关于Qt的工具的版本,各个概念
- 从普通dll导出lib
- C#的File类中常用的文件操作函数(方法)及其使用
- Intel, AMD及VIA CPU的微架构(11)
- 厉害了,苹果爸爸承认让旧 iPhone 变慢!
- 教您快速学会在Xshell中添加快捷命令
- MicrosoftHelpViewer查看器支持所有版本