欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android开发之OpenGL、OpenGL ES的概念和实例讲解

程序员文章站 2022-05-14 09:57:20
什么是opengl? open graphics library (opengl) is a cross-language, cross-platform application programmi...

什么是opengl?

open graphics library (opengl) is a cross-language, cross-platform application programming interface (api) for rendering 2d and 3d vector graphics. the api is typically used to interact with a graphics processing unit (gpu), to achieve hardware-accelerated rendering.

opengl是和语言,平台无关的一套interface,主要是为了rendering 2d和3d图形等。一般这套接口是用来和gpu进行交互的,使用gpu进行rendering硬件加速。

什么是opengl es?

android includes support for high performance 2d and 3d graphics with the open graphics library (opengl), specifically, the opengl es api. opengl is a cross-platform graphics api that specifies a standard software interface for 3d graphics processing hardware. opengl es is a flavor of the opengl specification intended for embedded devices.

opengl es就是专门为嵌入式设备设计的,当然售后机也是嵌入式,那么opengl es和opengl中的函数接口肯定有些是不一样的,因为嵌入式设备和pc等的硬件处理能力还是有差距的,不然手机卡死了。

既然opengl es只是一组函数接口,那么如何使用呢?我们肯定首先要去实现这些函数接口,而android提供了两种类型的实现:软件实现,硬件实现。

a, 硬件实现,前面提到这组函数接口主要是为了和gpu这个硬件进行打交道的。所以各个硬件厂商会提供相关的实现,例如高通平台的adreno解决方案;

b,软件实现,android也提供了一套opengl es的软件实现,就是说不用gpu了,完全用软件实现画图的相关功能,也就是libagl,代码在frameworks\native\opengl\libagl,其makefile中,

/软件实现最终编译完保存在system\lib\egl\libgles_android.so
local_module_path := $(target_out_shared_libraries)/egl
local_module:= libgles_android

到此,已经有了opengl es的具体实现,但是由于其实现的平台无关系,所以在android上还不能使用,必须借助egl。

egl

egl - native platform interface

egl is an interface between khronos rendering apis such as opengl es or openvg and the underlying native platform window system.

it handles graphics context management, surface/buffer binding, and rendering synchronization and enables high-performance, accelerated, mixed-mode 2d and 3d rendering using other khronos apis.

egl,它是图形渲染api(如opengl es)与本地平台窗口的一层接口,保证了opengl es的平*立性。egl提供了若干的功能:创建rendering surface,创建graphics context,同步应用程序和本地平台渲染api,提供对显示设备的访问,提供对渲染配置的管理等。

egl提供了一种方法用于通过客户端api和本地窗口系统进行渲染,客户端api包括用于嵌入式系统的3d渲染器和opengles,用于桌面系统的opengl es的超集opengl,2d矢量图形渲染器openvg,本地窗口系统包括windows,x

那么什么是egl?egl是opengl es和底层的native window system之间的接口,承上启下。

egl is a complement to opengl es. egl is used for getting surfaces to render to using functions like eglcreatewindowsurface, and you can then draw to that surface with opengl es. its role is similar to glx/wgl/cgl.

whether or not egl can give you a context that supports opengl es 2.0 may vary by platform, but if the android device supports es 2.0 and egl, you should be able to get such a context from egl. take a look at the egl_renderable_type attribute and the egl_opengl_es2_bit when requesting an eglconfig.

在android上,egl完善了opengl es。利用类似eglcreatewindowsurface的egl函数可以创建surface 用来render ,有了这个surface你就能往这个surface中利用opengl es函数去画图了。

egl要做什么

egl既然做平台和opengl es的中间层,那egl做的肯定就是和平台息息相关的事:

创建绘图窗口

也就是所谓的framebuffer,framebuffer可以先到到屏幕上(surfaceview) 创建渲染环境(context上下文)

渲染环境是指opengl es的所有项目运行需要的 数据结构。如顶点,片段着色器,顶点数据矩阵

opengl渲染一般流程:

Android开发之OpenGL、OpenGL ES的概念和实例讲解

eglconfig属性

属性 描述 默认值
egl_buffer_size 颜色缓冲器中所有组成演的的位数 0
egl_red_size 颜色缓冲区中红色位数 0
egl_luminance_size 颜色缓冲区中透明度的位数 0
egl_alptha_size 颜色缓冲区中透明位数 0
egl_alpthha_mask_size 遮挡缓冲区透明度掩码位数 0
egl_bind_to_texture_rgb 绑定到rgb贴图使能为真 egl_dont_care
egl_bind_to_texture_rgba 绑定到rgba贴图使能为真 egl_dont_care
egl_color_buffer_type 颜色缓冲区类型 egl_rgb_buffer, 或者egl_luminance_buffer egl_rgb_buffer
egl_config_caveat 配置有关的警告信息 egl_dont_care
egl_config_id 唯一的 eglconfig 标示值 egl_dont_care
egl_conformant 使用eglconfig 创建的上下文符合要求时为真 -
egl_depth_size 深度缓冲区位数 0
egl_level 帧缓冲区水平 0
egl_max_pbuffer_width 使用eglconfig 创建的pbuffer的最大宽度
egl_max_pbuffer_height 使用eglconfig 创建的pbuffer最大高度
egl_max_pbuffer_pixels 使用eglconfig 创建的pbuffer最大尺寸
egl_max_swap_interval 最大缓冲区交换间隔 egl_dont_care
egl_min_swap_interval 最小缓冲区交换间隔 egl_dont_care
egl_native_renderable 如果操作系统渲染库能够使用eglconfig 创建渲染渲染窗口 egl_dont_care
egl_native_visual_id 与操作系统通讯的可视id句柄 egl_dont_care
egl_native_visual_type 与操作系统通讯的可视id类型 egl_dont_care
egl_renderable_type 渲染窗口支持的布局组成标示符的遮挡位egl_opengl_es_bit, egl_opengl_es2_bit, oregl_openvg_bit that egl_opengl_es_bit
egl_sample_buffers 可用的多重采样缓冲区位数 0
egl_samples 每像素多重采样数 0
egl_s tencil_size 模板缓冲区位数 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

一个简单例子:

opengl的渲染是基于线程的,所以我们要创建一个gllrenderer类继承于handlerthread:

public class glrenderer extends handlerthread {

 private static final string tag = "glrenderer";
 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");
 }

 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());
  }

  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
  };//获取framebuffer格式和能力

  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上下文
  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());
  }
 }

 private void destorygl(){
  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() {
 destorygl();
 quit();
}
  });
 }

 /**
  * 加载制定shader的方法
  * @param shadertype shader的类型  gles20.gl_vertex_shadergles20.gl_fragment_shader
  * @param sourcecode shader的脚本
  * @return shader索引
  */
 private int loadshader(int shadertype,string sourcecode) {
  // 创建一个新shader
  int shader = gles20.glcreateshader(shadertype);
  // 若创建成功则加载shader
  if (shader != 0) {
// 加载shader的源代码
gles20.glshadersource(shader, sourcecode);
// 编译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程序的方法
  */
 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;
  }

  // 创建程序
  int program = gles20.glcreateprogram();
  // 若程序创建成功则向程序中加入顶点着色器与片元着色器
  if (program != 0) {
// 向程序中加入顶点着色器
gles20.glattachshader(program, vertexshader);
// 向程序中加入片元着色器
gles20.glattachshader(program, pixelshader);
// 链接程序
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;
 }

 /**
  * 获取图形的顶点
  * 特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过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;
 }

 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);

  // 初始化着色器
  // 基于顶点着色器与片元着色器创建程序
  program = createprogram(verticesshader, fragmentshader);
  // 获取着色器中的属性引用id(传入的字符串就是我们着色器脚本中的属性名)
  vposition = gles20.glgetattriblocation(program, "vposition");
  ucolor = gles20.glgetuniformlocation(program, "ucolor");

  // 设置clear color颜色rgba(这里仅仅是设置清屏时gles20.glclear()用的颜色值而不是执行清屏)
  gles20.glclearcolor(1.0f, 0, 0, 1.0f);
  // 设置绘图的窗口(可以理解成在画布上划出一块区域来画图)
  gles20.glviewport(0,0,width,height);
  // 获取图形的顶点坐标
  floatbuffer vertices = getvertices();

  // 清屏
  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);

  // 交换显存(将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" // 给此片元的填充色
+ "}";


}

creategl()方法获取了一个默认的显示设备(也就是手机屏幕),初始化并返回当前系统使用的opengl版本(主版本+子版本),然后通过配置(主要以键值对的方式配置,最后由egl_none结尾)得到一个eglconfig,最后创建一个eglcontext。

destroygl()方法则是释放掉opengl的资源(主要就是eglcontext)。

render()方法中主要是渲染,这里为了方便把渲染的环境和渲染写在一起并只渲染一次(我们只画了一个三角形),前三行代码我们创建了一个eglsurface并设置为当前的渲染对象,后面eglswapbuffers()交换了显示器和eglsurface的显存,也就是将我们渲染的东西放到显示器去显示,这样我们就看到我们绘制的三角形了,最后就是销毁我们创建的eglsurface。

然后在activity中使用它:

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();
 }
}

然后我们就可以得到一个:
Android开发之OpenGL、OpenGL ES的概念和实例讲解

然后我们继续学习:

egl创建eglsurface有三个方法:glcreatewindowsurface()、eglcreatepbuffersurface()和eglcreatepixmapsurface()。

windowsurface:是和窗口相关的,也就是在屏幕闪给的一块显示区的封装,渲染后即显示在界面上。 pbuffersurface:在显存中开辟一个控件,将渲染后的数据(帧)存放在这里 pixmapsurface:以位图的形式存放在内存中(支持性不好)

做离屏渲染我们可以选择pbuffersurface或者pixmapsurface(其实windowsurface也是可以的)

(帧缓存对象fbo是对帧缓存的封装,性能要优于pbuffer但并非可以完全替代pbuffer)。

opengl操作的最终目标实际上是帧缓存(frame buffer)后面的各种表现形式则是egl对frame buffer的封装
Android开发之OpenGL、OpenGL ES的概念和实例讲解

对之前的代码进行整理:

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

代码:

package com.example.asus1.camerawithopengl;

import android.opengl.egl14;
import android.opengl.eglsurface;
import android.view.surface;

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;
 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 viewport getviewport(){
  return viewport;
 }

}
public abstract class glrenderer1 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 eventqueue;
 private final list outputsurfaces;
 private boolean rendering;
 private boolean isrelease;

 public glrenderer1() {
  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 = egl14.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== egl14.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();//创建display,config,context,现在是没有surface的,只是将contxt联系起来
  oncreated();//得到program,postion,color和顶点的buffer
  // 渲染
  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) {
 log.d(tag, "run: 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 = egl14.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;
  }
 }



}

离屏渲染关键部分就是在makeoutputsurface()方法中的glsurface.type_pbuffer_surface这个case里面

其实// 交换显存(将surface显存和显示器的显存交换)
egl14.eglswapbuffers(egldisplay, output.eglsurface);对于离屏渲染是没有用的,因为本来就没有显示屏幕,于是我们可以这样:

package com.example.asus1.camerawithopengl;

import android.opengl.gles20;

import java.nio.bytebuffer;
import java.nio.byteorder;
import java.nio.floatbuffer;

public class testrenderer extends glrenderer1 {

 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 = shaderutil.createprogram(verticesshader,fragmentshader);
  vposition = gles20.glgetattriblocation(program,"vposition");
  ucolor = gles20.glgetuniformlocation(program,"ucolor");

  vertices = getvertices();

 }

 @override
 public void onupdate() {

 }

 @override
 public void ondrawframe(glsurface outputsurface) {
// 设置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中

  @override
 protected void oncreate(bundle savedinstancestate) {
  super.oncreate(savedinstancestate);

  setcontentview(r.layout.activity_main);
  final surfaceview surfaceview = (surfaceview)findviewbyid(r.id.surface);
  mimageview = (imageview)findviewbyid(r.id.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 i = intbuffer.allocate(512*512);
 gles20.glreadpixels(0, 0, 512, 512, gl10.gl_rgba, gl10.gl_unsigned_byte, i);

 final bitmap bitmap = frametobitmap(512,512,i);
 new handler(looper.getmainlooper()).post(new runnable() {
  @override
  public void run() {
mimageview.setimagebitmap(bitmap);
  }
 });
}
  });
  surfaceview.getholder().addcallback(new surfaceholder.callback() {
@override
public void surfacecreated(surfaceholder holder) {

}

@override
public void surfacechanged(surfaceholder holder, int format, int width, int height) {
 // glrenderer.render(holder.getsurface(),width,height);
 glsurface glwindowsurface = new glsurface(holder.getsurface(),width,height);
 glrenderer.addsurface(glwindowsurface);
 glrenderer.requestrender();


}

@override
public void surfacedestroyed(surfaceholder holder) {

}
  });



 }

 @override
 protected void ondestroy() {
 // mpreview.destorycamera();
  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);
 }

我们可以看到我们其实进行了两次渲染,第一次是离屏,第二次使用的是window_surface。为了看到离屏的效果,我们必须从opengl的线程中取出像素数据,然后转换成bitmap设置给imageview。
然后就能看到两个三角形了,顶部的小三角形就是在后台渲染的图像了

Android开发之OpenGL、OpenGL ES的概念和实例讲解

知识补充

display

egl提供了图形api如opengl es和原生窗口系统如linux 的x window之间的一个结合层次,在egl能够确定可用的surface类型之前,它必须打开和窗口系统的通信渠道,因为是跨平台的,每个窗口系统都有不同的遗言,所以egl提供基本的不透明类型的egldisplai,该类型封装了所有系统的相关属性,用于原生窗口的系统接口,不同的窗口系统定义了不同的display属性,最终都会native化。任何使用egl的应用程序必须执行的第一个操作是创建与初始化与本地egl display的连接:

 eglgetdisplay(eglnativedisplaytype display_id);
 eglinitialize(egldisplay dpy, eglint *major, eglint *minor)

eglgetdisplay用于获取egl display连接,display_id指定需要连接的display,一般取默认值egl_default_display
eglinitialize用于对display进行初始化,初始化已经初始化了display对这个display没有影响,major和minor获取当前的egl版本号

#配置surface

egl初始化display完成后,就可以对surface进行配置了,一种是查询每个surface配置,找出最好的选择,另一种是指定一组需求,让egl推荐最佳匹配,两者都返回一个eglconfig,包括surface相关的所有属性。

 eglgetconfigs(egldisplay dpy, eglconfig *configs,
 eglint config_size, eglint *num_config);
 eglgetconfigattrib(egldisplay dpy, eglconfig config,
eglint attribute, eglint *value);
 eglchooseconfig(egldisplay dpy, const eglint *attrib_list,
eglconfig *configs, eglint config_size,
eglint *num_config);

eglgetconfigs用于获取display的frame buffer配置列表,dpy为对应的display,configs返回配置列表(可以为null而只是通过num_config获取配置列表可用的配置条目),size指定配置列表的大小(size大于1时configs需要有足够的存储空间),num_config返回配置列表获取的配置条目。

eglgetconfigattrib用于获取egl的frame buffer配置列表中某个具体属性的值,dpy为对应的display,config为待查询的配置列表,attribute为待查询的具体属性,value返回查询的属性值,

eglchooseconfig用于获取display的frame buffer配置列表,不过这个配置列表要与指定的属性列表attrib_list匹配,dpy为对应的display,attrib_list为config需要匹配的属性列表,configs返回配置列表(非null时,size为最大值,num_configs为实际值,为null时,忽略size),size指定配置列表的大小(size大于1时configs需要有足够的存储空间),num_config返回配置列表获取的配置条目

创建surface

window surface:可以理解为egl窗口,是屏幕上的渲染区域,有eglcreatewindowsurface创建

eglcreatewindowsurface(egldisplay dpy, eglconfig config,
eglnativewindowtype win,
const eglint *attrib_list);

eglcreatewindowsurface用于创建window surface,dpy为对应的egl display连接,config为egl frame buffer配置,定义了可用于surface的frame buffer资源,win为native window,是个平台相关的类型,attrib_list为window surface属性列表,可以为null。

egldestroysurface(egldisplay dpy, eglsurface surface

用于销毁surface,dpy为对应的display,surface为将要销毁的surface,如果任何其它线程都没有使用这个surface时,surface将被立即销毁,否则要等到这个surface不被任何线程使用时才销毁,另外,对于一个pbuffer surface来说,其资源要等到绑定到纹理对象的颜色缓冲区释放后才被销毁。

glquerysurface(egldisplay dpy, eglsurface surface,
eglint attribute, eglint *value);

于获取surface信息,dpy为对应的display,surface待查询的surface,attribute为待查询的surface属性,value用于返回surface属性值

pbuffer surface:称作pbuffer(像素缓冲区pixel buffer的缩写)的不可见屏幕外表面,和窗口一样,pbuffer可以利用opengl es 3.0中的任何硬件加速,pbuffer最常用于生成纹理贴图,如果想要做的是渲染到一个纹理,那么建议使用帧缓冲区对象(fbo)代替pbuffer,因为帧缓冲区更高效,不过在某些fbo无法使用的情况下,pbuffer仍然有用,

glcreatepbuffersurface(egldisplay dpy, eglconfig config,
 const eglint *attrib_list);

eglcreatepbuffersurface用于创建off-screen的pixel buffer surface,dpy为对应的display,config为frame buffer配置,attrib_list为pbuffer属性列表,可以为null

pixmap surface:

eglcreatepixmapsurface(egldisplay dpy, eglconfig config,
eglnativepixmaptype pixmap,
const eglint *attrib_list);

用于创建off-screen的pixmap surface,dpy为对应的display,config为frame buffer配置,pixmap为native pixmap,attrib_list为pixmap属性列表,可以为null

三种surface的区别
window是on-screen的,
pbuffer和pixmap是off-screen的, window绑定到了nativewindow
,pixmap绑定到了nativepixmap,
pbuffer没有任何本地绑定 window是双缓冲区的
pbuffer和pixmap是单缓冲区的, window默认在back buffer渲染,通过eglswapbuffers交换到屏幕上显示,
pbuffer在显存中分配,由egl_height和egl_width指定大小,常用作纹理数据,
pixmap绑定到本地的像素缓冲区,这个缓冲区可以被其它api使用。 创建不同的eglsurface时,需要在eglconfig中配置egl_surface_type,window、pbuffer、pixmap分别对应于egl_window_bit、egl_pbuffer_bit、egl_pixmap_buffer。
eglswapbuffers—— 

对于window surface或back buffer来说,还需要通过eglswapbuffers把off-sreen的back buffer交换到screen buffer,也就是说把egl surface的颜色缓冲区post到native window,内部调用了渲染api的flush命令,返回egl_false时可能的原因为egl_bad_display、egl_not_initialized、egl_bad_surface、egl_context_lost。
eglswapbuffers对pbuffer surface和pixmap surface无效。

创建context
eglcreatecontext(egldisplay dpy, eglconfig config,
 eglcontext share_context,
 const eglint *attrib_list);

eglcreatecontext用于创建egl渲染context,dpy为对应的display,config为frame buffer配置,share_context为其它的共享context,可以设置为egl_no_context,attrib_list为context属性列表,成功时返回新创建的eglcontext,失败时返回egl_no_context

ttrib_list属性目前只有egl_context_client_version,整数值,指定opengl es版本,默认值为1,还可以是2、3等其它版本值,创建opengl es context时设置这个属性,也就是说渲染api为egl_opengl_es_api时才设置这个属性

eglmakecurrent(egldisplay dpy, eglsurface draw,
  eglsurface read, eglcontext ctx);

创建了surface和context之后,因为可能有多个surface和context,所以需要通过eglmakecurrent绑定context到surface,dpy为对应的display,draw用于绘制,read用于读,ctx为要绑定的context