当前位置: 首页 > news >正文

【OpenGL ES】不用GLSurfaceView,如何渲染图像

1 前言

        Android 中,GLSurfaceView 封装了 EGL 环境,使得我们省去了复杂的 EGL 环境搭建。如果我们不用 GLSurfaceView,该如何渲染 OpenGL ES 图像?在回答此问题前,我们先了解下 EGL。

        EGL 是 Khronos Group 定义的平台无关接口,作为 OpenGL ES 和本地窗口系统之间的桥梁,主要功能如下。

  • 管理图形上下文
  • 创建和管理渲染表面 (surface)
  • 同步渲染与平台显示系统

        EGL 提供了两种渲染方式,分别是离屏渲染和窗口渲染,分别对应 eglCreatePbufferSurface 和 eglCreateWindowSurface。对于离屏渲染方案,详见 → EGL+FBO离屏渲染。eglCreateWindowSurface 函数的源码如下。

public static EGLSurface eglCreateWindowSurface(EGLDisplay dpy,EGLConfig config, Object win, int[] attrib_list, int offset){Surface sur = null;if (win instanceof SurfaceView) {SurfaceView surfaceView = (SurfaceView)win;sur = surfaceView.getHolder().getSurface();} else if (win instanceof SurfaceHolder) {SurfaceHolder holder = (SurfaceHolder)win;sur = holder.getSurface();} else if (win instanceof Surface) {sur = (Surface) win;}EGLSurface surface;if (sur != null) {surface = _eglCreateWindowSurface(dpy, config, sur, attrib_list, offset);} else if (win instanceof SurfaceTexture) {surface = _eglCreateWindowSurfaceTexture(dpy, config,win, attrib_list, offset);} else {throw new java.lang.UnsupportedOperationException("eglCreateWindowSurface() can only be called with an instance of " +"Surface, SurfaceView, SurfaceTexture or SurfaceHolder at the moment, " +"this will be fixed later.");}return surface;
}

        主要留意 win 参数,可以看到它可以是 SurfaceView、SurfaceHolder、Surface,本质都是为了获取 Surface。因此我们提供了以下两种渲染图像的方案。

  • 继承 SurfaceView 方案:自定义一个 View 继承 SurfaceView,并实现 SurfaceHolder.Callback 接口,在 surfaceCreated 方法中将 this 或 getHolder() 传给 eglCreateWindowSurface 函数。
  • 继承 TextureView 方案:自定义一个 View 继承 TextureView,并实现 TextureView.SurfaceTextureListener 接口,在 onSurfaceTextureAvailable 方法中会提供 SurfaceTexture,我们可以创建一个 Surface,并将 SurfaceTexture 传给 Surface,然后将创建的 Surface 传给 eglCreateWindowSurface 函数。

        本文完整代码详见 → 不用GLSurfaceView,如何渲染图像。

2 继承 SurfaceView 方案

        自定义一个 View 继承 SurfaceView,并实现 SurfaceHolder.Callback 接口,在 surfaceCreated 方法中将 this 或 getHolder() 传给 eglCreateWindowSurface 函数。

        由于 GLSurfaceView 继承 SurfaceView,所以该方案很容易想到,只需要将 GLSurfaceView 的核心代码扣出来就行。

        MainActivity.java

package com.zhyan8.egldemo;import android.os.Bundle;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private EGLSurfaceView mEglSurfaceView;protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mEglSurfaceView = new EGLSurfaceView(this);setContentView(mEglSurfaceView);mEglSurfaceView.setRenderer(new MyRenderer(this));mEglSurfaceView.requestRender();//mEglSurfaceView.startRender();}@Overrideprotected void onResume() {super.onResume();mEglSurfaceView.requestRender();//mEglSurfaceView.startRender();}@Overrideprotected void onPause() {super.onPause();mEglSurfaceView.stopRender();}
}

        EGLSurfaceView.java

package com.zhyan8.egldemo;import android.content.Context;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLExt;
import android.opengl.EGLSurface;
import android.util.Log;
import android.view.Choreographer;
import android.view.SurfaceHolder;
import android.view.SurfaceView;import androidx.annotation.NonNull;/*** @author little fat sheep* 承载EGL环境的View, 类比GLSurfaceView*/
public class EGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {private static final String TAG = "EGLSurfaceView";protected EGLDisplay mEGLDisplay;protected EGLConfig mEGLConfig;protected EGLContext mEGLContext;protected EGLSurface mEGLSurface;protected Context mContext;protected Renderer mRenderer;protected boolean mFirstCreateSurface = true;public EGLSurfaceView(Context context) {super(context);mContext = context;getHolder().addCallback(this);}// 设置渲染器public void setRenderer(Renderer renderer) {mRenderer = renderer;}// 开始持续渲染public void startRender() {Choreographer.getInstance().postFrameCallback(mFrameCallback);}// 暂停持续渲染public void stopRender() {Choreographer.getInstance().removeFrameCallback(mFrameCallback);}// 请求渲染一帧public void requestRender() {mFrameCallback.doFrame(System.nanoTime());}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();createDisplay();createConfig();createContext();}@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) { // 每次activity resume都会调用一次Log.i(TAG, "surfaceCreated, surface=" + holder.getSurface());createSurface();makeCurrent();if (mFirstCreateSurface) {mRenderer.onSurfaceCreated();mFirstCreateSurface = false;}}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {Log.i(TAG, "surfaceChanged, width=" + width + ", height=" + height);mRenderer.onSurfaceChanged(width, height);}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) { // 每次activity pause都会调用一次Log.i(TAG, "surfaceDestroyed");if (mEGLDisplay != null && mEGLDisplay != EGL14.EGL_NO_DISPLAY) {// 与显示设备解绑EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);// 销毁 EGLSurfaceif (mEGLSurface != null && mEGLSurface != EGL14.EGL_NO_SURFACE) {EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);checkoutConfig("eglDestroySurface");mEGLSurface = null;}}}@Overridepublic void onDetachedFromWindow() {super.onDetachedFromWindow();Log.i(TAG, "onDetachedFromWindow");getHolder().removeCallback(this);if (mEGLDisplay != null && mEGLDisplay != EGL14.EGL_NO_DISPLAY) {// 与显示设备解绑EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);// 销毁 EGLSurfaceif (mEGLSurface != null && mEGLSurface != EGL14.EGL_NO_SURFACE) {EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);checkoutConfig("eglDestroySurface");mEGLSurface = null;}// 销毁 EGLContextif (mEGLContext != null && mEGLContext != EGL14.EGL_NO_CONTEXT) {EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);checkoutConfig("eglDestroyContext");mEGLContext = null;}// 销毁 EGLDisplay (显示设备)EGL14.eglTerminate(mEGLDisplay);checkoutConfig("eglTerminate");mEGLDisplay = null;}}// 1.创建EGLDisplayprivate void createDisplay() {mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);int[] versions = new int[2];EGL14.eglInitialize(mEGLDisplay, versions,0, versions, 1);checkoutConfig("eglInitialize");}// 2.创建EGLConfigprivate void createConfig() {if (mEGLDisplay != null && mEGLDisplay != EGL14.EGL_NO_DISPLAY) {EGLConfig[] configs = new EGLConfig[1];int[] configNum = new int[1];EGL14.eglChooseConfig(mEGLDisplay, mEGLConfigAttrs, 0, configs, 0,1,  configNum, 0);if (configNum[0] > 0) {mEGLConfig = configs[0];}checkoutConfig("eglChooseConfig");}}// 3.创建EGLContextprivate void createContext() {if (mEGLConfig != null) {mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, mEGLContextAttrs, 0);checkoutConfig("eglCreateContext");}}// 4.创建EGLSurfaceprivate void createSurface() {if (mEGLContext != null && mEGLContext != EGL14.EGL_NO_CONTEXT) {int[] eglSurfaceAttrs = { EGL14.EGL_NONE };//mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, getHolder(), eglSurfaceAttrs, 0);mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, this, eglSurfaceAttrs, 0);checkoutConfig("eglCreateWindowSurface");}}// 5.绑定EGLSurface和EGLContext到显示设备(EGLDisplay)private void makeCurrent() {if (mEGLSurface != null && mEGLSurface != EGL14.EGL_NO_SURFACE) {EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);checkoutConfig("eglMakeCurrent");}}private void checkoutConfig(String tag) {int error = EGL14.eglGetError();if (error != EGL14.EGL_SUCCESS) {Log.e(TAG, tag + " error=0x" + Integer.toHexString(error));}}// EGLConfig参数private int[] mEGLConfigAttrs = {EGL14.EGL_RED_SIZE, 8,EGL14.EGL_GREEN_SIZE, 8,EGL14.EGL_BLUE_SIZE, 8,EGL14.EGL_ALPHA_SIZE, 8,EGL14.EGL_DEPTH_SIZE, 8,//EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,EGL14.EGL_RENDERABLE_TYPE, EGLExt.EGL_OPENGL_ES3_BIT_KHR,EGL14.EGL_NONE};// EGLContext参数private int[] mEGLContextAttrs = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,EGL14.EGL_NONE};Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {@Overridepublic void doFrame(long frameTimeNanos) {Choreographer.getInstance().postFrameCallback(mFrameCallback);if (mEGLSurface != null) {mRenderer.onDrawFrame();EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);checkoutConfig("eglSwapBuffers");}}};/*** @author little fat sheep* 渲染器接口, 类比GLSurfaceView.Renderer*/interface Renderer {void onSurfaceCreated();void onSurfaceChanged(int width, int height);void onDrawFrame();}
}

        MyRenderer.java

package com.zhyan8.egldemo;import android.content.Context;
import android.opengl.GLES30;import java.nio.FloatBuffer;public class MyRenderer implements EGLSurfaceView.Renderer {private FloatBuffer vertexBuffer;private FloatBuffer textureBuffer;private MyGLUtils mGLUtils;private int mTextureId;public MyRenderer(Context context) {mGLUtils = new MyGLUtils(context);getFloatBuffer();}@Overridepublic void onSurfaceCreated() {//设置背景颜色GLES30.glClearColor(0.1f, 0.2f, 0.3f, 0.4f);//编译着色器final int vertexShaderId = mGLUtils.compileShader(GLES30.GL_VERTEX_SHADER, R.raw.vertex_shader);final int fragmentShaderId = mGLUtils.compileShader(GLES30.GL_FRAGMENT_SHADER, R.raw.fragment_shader);//链接程序片段int programId = mGLUtils.linkProgram(vertexShaderId, fragmentShaderId);GLES30.glUseProgram(programId);mTextureId = mGLUtils.loadTexture(R.raw.girl);}@Overridepublic void onSurfaceChanged(int width, int height) {//设置视图窗口GLES30.glViewport(0, 0, width, height);}@Overridepublic void onDrawFrame() {//将颜色缓冲区设置为预设的颜色GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);//启用顶点的数组句柄GLES30.glEnableVertexAttribArray(0);GLES30.glEnableVertexAttribArray(1);//准备顶点坐标和纹理坐标GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);GLES30.glVertexAttribPointer(1, 2, GLES30.GL_FLOAT, false, 0, textureBuffer);//激活纹理GLES30.glActiveTexture(GLES30.GL_TEXTURE);//绑定纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureId);//绘制贴图GLES30.glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, 4);//禁止顶点数组句柄GLES30.glDisableVertexAttribArray(0);GLES30.glDisableVertexAttribArray(1);}private void getFloatBuffer() {float[] vertex = new float[] {1f, 1f, 0f,     //V0-1f, 1f, 0f,    //V1-1f, -1f, 0f,   //V21f, -1f, 0f     //V3};float[] texture = {1f, 0f,     //V00f, 0f,     //V10f, 1.0f,   //V21f, 1.0f    //V3};vertexBuffer = mGLUtils.getFloatBuffer(vertex);textureBuffer = mGLUtils.getFloatBuffer(texture);}
}

        MyGLUtils.java

package com.zhyan8.egldemo;import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES30;
import android.opengl.GLUtils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;public class MyGLUtils {private Context mContext;private Bitmap mBitmap;public MyGLUtils(Context context) {mContext = context;}public FloatBuffer getFloatBuffer(float[] floatArr) {FloatBuffer fb = ByteBuffer.allocateDirect(floatArr.length * Float.BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();fb.put(floatArr);fb.position(0);return fb;}//通过代码片段编译着色器public int compileShader(int type, String shaderCode){int shader = GLES30.glCreateShader(type);GLES30.glShaderSource(shader, shaderCode);GLES30.glCompileShader(shader);return shader;}//通过外部资源编译着色器public int compileShader(int type, int shaderId){String shaderCode = readShaderFromResource(shaderId);return compileShader(type, shaderCode);}//链接到着色器public int linkProgram(int vertexShaderId, int fragmentShaderId) {final int programId = GLES30.glCreateProgram();//将顶点着色器加入到程序GLES30.glAttachShader(programId, vertexShaderId);//将片元着色器加入到程序GLES30.glAttachShader(programId, fragmentShaderId);//链接着色器程序GLES30.glLinkProgram(programId);return programId;}//从shader文件读出字符串private String readShaderFromResource(int shaderId) {InputStream is = mContext.getResources().openRawResource(shaderId);BufferedReader br = new BufferedReader(new InputStreamReader(is));String line;StringBuilder sb = new StringBuilder();try {while ((line = br.readLine()) != null) {sb.append(line);sb.append("\n");}br.close();} catch (Exception e) {e.printStackTrace();}return sb.toString();}//加载纹理贴图public int loadTexture(int resourceId) {BitmapFactory.Options options = new BitmapFactory.Options();options.inScaled = false;mBitmap = BitmapFactory.decodeResource(mContext.getResources(), resourceId, options);final int[] textureIds = new int[1];// 生成纹理idGLES30.glGenTextures(1, textureIds, 0);// 绑定纹理到OpenGLGLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds[0]);GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR_MIPMAP_LINEAR);GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);// 加载bitmap到纹理中GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, mBitmap, 0);// 生成MIP贴图GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D);// 取消绑定纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);return textureIds[0];}
}

        vertex_shader.glsl

precision mediump float;
uniform sampler2D uTextureUnit;
varying vec2 vTexCoord;
void main() {gl_FragColor = texture2D(uTextureUnit, vTexCoord);
}

        fragment_shader.glsl

attribute vec4 aPosition;
attribute vec2 aTextureCoord;
varying vec2 vTexCoord;
void main() {gl_Position = aPosition;vTexCoord = aTextureCoord;
}

        运行效果如下。

3 继承 TextureView 方案

        自定义一个 View 继承 TextureView,并实现 TextureView.SurfaceTextureListener 接口,在 onSurfaceTextureAvailable 方法中会提供 SurfaceTexture,我们可以创建一个 Surface,并将 SurfaceTexture 传给 Surface,然后将创建的 Surface 传给 eglCreateWindowSurface 函数。

        前段时间在看 Rive 的源码,详见 → rive-android源码分析,了解到 Rive 底层是通过 OpenGL ES 渲染图像,并且也没有使用 GLSurfaceView,由此借鉴而来。该方案主要参考 Rive 中 RiveTextureView 的实现,eglCreateWindowSurface 参考 thread_state_egl.cpp。

        与第二节相比,只有 EGLSurfaceView 类有差异,因此本节仅展示 EGLSurfaceView 的代码。

        EGLSurfaceView.java

package com.zhyan8.egldemo;import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLExt;
import android.opengl.EGLSurface;
import android.util.Log;
import android.view.Choreographer;
import android.view.Surface;
import android.view.TextureView;import androidx.annotation.NonNull;/*** @author little fat sheep* 承载EGL环境的View, 类比GLSurfaceView*/
public class EGLSurfaceView extends TextureView implements TextureView.SurfaceTextureListener {private static final String TAG = "EGLSurfaceView";protected EGLDisplay mEGLDisplay;protected EGLConfig mEGLConfig;protected EGLContext mEGLContext;protected EGLSurface mEGLSurface;protected Context mContext;protected Surface mSurface;protected Renderer mRenderer;public EGLSurfaceView(Context context) {super(context);mContext = context;setSurfaceTextureListener(this);}// 设置渲染器public void setRenderer(Renderer renderer) {mRenderer = renderer;}// 开始持续渲染public void startRender() {Choreographer.getInstance().postFrameCallback(mFrameCallback);}// 暂停持续渲染public void stopRender() {Choreographer.getInstance().removeFrameCallback(mFrameCallback);}// 请求渲染一帧public void requestRender() {mFrameCallback.doFrame(System.nanoTime());}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();Log.i(TAG, "onAttachedToWindow");createDisplay();createConfig();createContext();}@Overridepublic void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {Log.i(TAG, "onSurfaceTextureAvailable");mSurface = new Surface(surface);createSurface();makeCurrent();mRenderer.onSurfaceCreated();}@Overridepublic void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {Log.i(TAG, "onSurfaceTextureSizeChanged, width=" + width + ", height=" + height);mRenderer.onSurfaceChanged(width, height);}@Overridepublic boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {Log.i(TAG, "onSurfaceTextureDestroyed");return false;}@Overridepublic void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();Log.i(TAG, "onDetachedFromWindow");setSurfaceTextureListener(null);mSurface.release();if (mEGLDisplay != null && mEGLDisplay != EGL14.EGL_NO_DISPLAY) {// 与显示设备解绑EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);// 销毁 EGLSurfaceif (mEGLSurface != null && mEGLSurface != EGL14.EGL_NO_SURFACE) {EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);checkoutConfig("eglDestroySurface");mEGLSurface = null;}// 销毁 EGLContextif (mEGLContext != null && mEGLContext != EGL14.EGL_NO_CONTEXT) {EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);checkoutConfig("eglDestroyContext");mEGLContext = null;}// 销毁 EGLDisplay (显示设备)EGL14.eglTerminate(mEGLDisplay);checkoutConfig("eglTerminate");mEGLDisplay = null;}}// 1.创建EGLDisplayprivate void createDisplay() {mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);int[] versions = new int[2];EGL14.eglInitialize(mEGLDisplay, versions,0, versions, 1);checkoutConfig("eglInitialize");}// 2.创建EGLConfigprivate void createConfig() {if (mEGLDisplay != null && mEGLDisplay != EGL14.EGL_NO_DISPLAY) {EGLConfig[] configs = new EGLConfig[1];int[] configNum = new int[1];EGL14.eglChooseConfig(mEGLDisplay, mEGLConfigAttrs, 0, configs, 0,1,  configNum, 0);if (configNum[0] > 0) {mEGLConfig = configs[0];}checkoutConfig("eglChooseConfig");}}// 3.创建EGLContextprivate void createContext() {if (mEGLConfig != null) {mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, mEGLContextAttrs, 0);checkoutConfig("eglCreateContext");}}// 4.创建EGLSurfaceprivate void createSurface() {if (mEGLContext != null && mEGLContext != EGL14.EGL_NO_CONTEXT) {int[] eglSurfaceAttrs = { EGL14.EGL_NONE };mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mSurface, eglSurfaceAttrs, 0);checkoutConfig("eglCreateWindowSurface");}}// 5.绑定EGLSurface和EGLContext到显示设备(EGLDisplay)private void makeCurrent() {if (mEGLSurface != null && mEGLSurface != EGL14.EGL_NO_SURFACE) {EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);checkoutConfig("eglMakeCurrent");}}private void checkoutConfig(String tag) {int error = EGL14.eglGetError();if (error != EGL14.EGL_SUCCESS) {Log.e(TAG, tag + " error=0x" + Integer.toHexString(error));}}// EGLConfig参数private int[] mEGLConfigAttrs = {EGL14.EGL_RED_SIZE, 8,EGL14.EGL_GREEN_SIZE, 8,EGL14.EGL_BLUE_SIZE, 8,EGL14.EGL_ALPHA_SIZE, 8,EGL14.EGL_DEPTH_SIZE, 8,//EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,EGL14.EGL_RENDERABLE_TYPE, EGLExt.EGL_OPENGL_ES3_BIT_KHR,EGL14.EGL_NONE};// EGLContext参数private int[] mEGLContextAttrs = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,EGL14.EGL_NONE};Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {@Overridepublic void doFrame(long frameTimeNanos) {Choreographer.getInstance().postFrameCallback(mFrameCallback);if (mEGLSurface != null) {mRenderer.onDrawFrame();EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);checkoutConfig("eglSwapBuffers");}}};/*** @author little fat sheep* 渲染器接口, 类比GLSurfaceView.Renderer*/interface Renderer {void onSurfaceCreated();void onSurfaceChanged(int width, int height);void onDrawFrame();}
}

        运行效果如下。

http://www.lryc.cn/news/571724.html

相关文章:

  • java学习笔记 IDEA的相关配置
  • 基于Android的打印系统的设计与实现
  • 深入解析 Java List 实现类的底层原理
  • 软件技术专业的出路在哪
  • 学习量子网络中的最佳路径
  • 华为云Flexus+DeepSeek征文 | 基于DeepSeek-R1强化学习的多模态AI Agent企业级应用开发实战:从理论到生产的完整解决方案
  • 使用 Visual Studio 创建安装包的完整指南
  • Saucer 页面嵌入使用举例
  • MySQL 8.0 OCP 题库完整版
  • 【Git】Git生产项目分支管理实战指南包含开发、测试、生产、bug修复和需求迭代
  • SHELL脚本(一)
  • 【微信小程序】4、SpringBoot整合WxJava生成小程序码
  • github为InfiniSynapse Docker提PR过程留档@Windows10
  • mysql 根据查询语句创建表语句
  • windows内网穿透
  • tauri+vue自动更新客户端打包配置
  • crackme009
  • 算法导论第十四章 B树与B+树:海量数据的守护者
  • TensorFlow基础之理解计算图
  • HBase RowKey设计原则.注意什么
  • [攻略本] 塞尔达系列攻略本/设定集PDF格式7.5GB
  • 探究webView与html的通讯
  • 腾讯云TCCA认证考试报名 - TDSQL数据库交付运维工程师(MySQL版)
  • 数学符号和标识中英文列表(含义与示例)
  • Vue-9-前端框架Vue之应用基础watch监视和watcheffect监视
  • 深入理解链表数据结构:从Java LinkedList到自定义实现
  • shelve模块的使用
  • 论文阅读笔记 | Qwen-VL:一个视觉语言大模型,通晓理解、定位、文本阅读等多种能力
  • 基于 Python Django 框架的在线租房管理系统设计与实现
  • ROS2 笔记汇总(2) 通信接口