Android OpenGLES3绘图:EGL无屏绘制
一般渲染绘图都是在GLSurfaceView上,查看源码可知道,GLSurfaceView就是继承自SurfaceView,并且自己管理了OpenGL环境。OpenGL绘制调用的都是静态方法,所以OpenGL环境默认是跟创建它的线程绑定,然后在这个线程调用OpenGL的方法才有效果。
在某些情况下,需要脱离GLSurfaceView使用OpenGL绘制,比如WebRTC视频添加滤镜,它需要采集视频然后用OpenGL处理,处理后再展示到本地并且编码传输。这时就需要自己创建OpenGL环境,主要是创建EGL配置和线程。
1.创建EGL配置
先创建EGLDisplay、EGLContext、EGLConfig这三个对象,创建同时检查是否创建成功。
public EglCore() {
mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL14 display");
}
int[] version = new int[2];
if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 0)) {
mEglDisplay = null;
throw new RuntimeException("unable to initialize EGL14");
}
String vendor = EGL14.eglQueryString(mEglDisplay, EGL14.EGL_VENDOR);
Log.d("chao", "egl vendor: " + vendor); // 打印此版本EGL的实现厂商
String versionStr = EGL14.eglQueryString(mEglDisplay, EGL14.EGL_VERSION);
Log.d("chao", "egl version: " + versionStr);// 打印EGL版本号
String extension = EGL14.eglQueryString(mEglDisplay, EGL14.EGL_EXTENSIONS);
Log.d("chao", "egl extension: " + extension); //打印支持的EGL扩展
int[] attributes = new int[] {
EGL14.EGL_RED_SIZE, 8, //指定RGB中的R大小(bits)
EGL14.EGL_GREEN_SIZE, 8, //指定G大小
EGL14.EGL_BLUE_SIZE, 8, //指定B大小
EGL14.EGL_ALPHA_SIZE, 8, //指定Alpha大小,以上四项实际上指定了像素格式
EGL14.EGL_DEPTH_SIZE, 16, //指定深度缓存(Z Buffer)大小
EGL14.EGL_RENDERABLE_TYPE, 4, //指定渲染api类别,这里或者是硬编码的4,或者是EGL14.EGL_OPENGL_ES2_BIT
EGL14.EGL_NONE }; //总是以EGL10.EGL_NONE结尾
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
if (!EGL14.eglChooseConfig(mEglDisplay, attributes, 0, configs, 0, configs.length, numConfigs, 0)) { //获取所有
Log.w("chao", "unable to find RGB8888 suitable EGLConfig");
}
mEglConfig = configs[0];
int[] attrs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE };
mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig, EGL14.EGL_NO_CONTEXT, attrs, 0);
}
然后创建EGLSurface,并且绑定到当前环境。
EGLSurface createPbufferSurface(int w, int h) {
int [] attrs = {
EGL14.EGL_WIDTH, w,
EGL14.EGL_HEIGHT, h,
EGL14.EGL_NONE
};
EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attrs, 0);
if (eglSurface == null) {
throw new RuntimeException("eglCreatePbufferSurface failed!");
}
return eglSurface;
}
public void makeCurrent(int w, int h) {
mEglSurface = createPbufferSurface(w, h);
if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
Log.e("chao", "eglMakeCurrent failed");
}
}
2.创建EGL线程
其实就是创建一个HandlerThread,在这个线程里调用上面的方法初始化EGL,然后不停地调用着色器的绘制方法。
public class EglThread {
Handler mHandler;
EglCore mEglCore;
public EglThread(String name) {
HandlerThread ht = new HandlerThread(name);
ht.start();
mHandler = new Handler(ht.getLooper());
}
public void initEglCore(int w, int h) {
if (mEglCore == null) {
mHandler.post(() -> {
mEglCore = new EglCore();
mEglCore.makeCurrent(w, h);
});
}
}
public void post(Runnable r) {
if (mHandler != null) {
mHandler.post(r);
}
}
public void destroy() {
if (mHandler != null) {
mHandler.post(() -> {
mEglCore.release();
mHandler.getLooper().quitSafely();
});
}
}
}
3.使用EGL绘制
实际绘制一下来检验效果,找一个之前使用的SimpleRender,它是一个简单的OpenGL绘制图片的着色器,图片可以不停转动。
在eglThread里面调用
simpleRender.onSurfaceCreated(null, null);
simpleRender.onSurfaceChanged(null, w, h);
这俩方法初始化着色器,然后在while循环里反复调用
simpleRender.onDrawFrame(null);
就可以不停绘制了。
GLES20.glReadPixels
方法可以从OpenGL里面获取绘制后的结果,转换成Bitmap就可以展示到ImageView上面。
public class EglActivity extends AppCompatActivity {
ImageView imageView;
EglThread eglThread;
SimpleRender simpleRender;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
imageView = new ImageView(this);
imageView.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
setContentView(imageView);
imageView.setBackgroundColor(Color.BLUE);
simpleRender = new SimpleRender();
eglThread = new EglThread("chao-gl-thread");
imageView.post(() -> {
int w = imageView.getWidth();
int h = imageView.getHeight();
eglThread.initEglCore(w, h);
eglThread.post(() -> {
simpleRender.onSurfaceCreated(null, null);
simpleRender.onSurfaceChanged(null, w, h);
while(!EglActivity.this.isFinishing() && !EglActivity.this.isDestroyed()) {
simpleRender.onDrawFrame(null);
Bitmap bitmap = getBitmapFromGL(w, h);
imageView.post(() -> {
imageView.setImageBitmap(bitmap);
});
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
});
}
Bitmap getBitmapFromGL(int w, int h) {
IntBuffer intBuffer = IntBuffer.allocate(w * h);
GLES20.glReadPixels(0, 0, w, h, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, intBuffer);
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(intBuffer);
return bitmap;
}
@Override
protected void onDestroy() {
super.onDestroy();
eglThread.destroy();
}
}
虽然用的是ImageView,但是实现效果跟用GLSurfaceView完全相同。
4 Github地址
完整项目在SurfacePaint项目下的opengles3
模块里。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性