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硬件加速。
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 - 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既然做平台和OpenGL ES的中间层,那EGL做的肯定就是和平台息息相关的事:
属性 | 描述 | 默认值 |
---|---|---|
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_SHADER GLES20.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();
}
}
然后我们继续学习:
EGL创建EGLSurface有三个方法:glCreateWindowSurface()、eglCreatePbufferSurface()和eglCreatePixmapSurface()。
做离屏渲染我们可以选择PbufferSurface或者PixmapSurface(其实WindowSurface也是可以的)
(帧缓存对象FBO是对帧缓存的封装,性能要优于Pbuffer但并非可以完全替代Pbuffer)。
OpenGL操作的最终目标实际上是帧缓存(Frame Buffer)后面的各种表现形式则是EGL对Frame Buffer的封装
对之前的代码进行整理:
代码:
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。
然后就能看到两个三角形了,顶部的小三角形就是在后台渲染的图像了
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版本号
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返回配置列表获取的配置条目
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
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无效。
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
参考:https://www.jianshu.com/p/8793f0fbd1e6
https://blog.csdn.net/iEearth/article/details/71180457