添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Android NDK-EGL 初级

在最近的工作中,发现很少有资料直接介绍android EGL的。

在翻越GLSurfaceView和Skia的源码之后,将我自己的NDK-EGL编程整理如下,供有需要的开发者取用

什么是EGL

EGL at a glance
EGL provides mechanisms for creating rendering surfaces onto which client APIs like OpenGL ES and OpenVG can draw, creates graphics contexts for client APIs, and synchronizes drawing by client APIs as well as native platform rendering APIs. This enables seamless rendering using both OpenGL ES and OpenVG for high-performance, accelerated, mixed-mode 2D and 3D rendering.
EGL概览
EGL提供了创建surface的机制。该机制使得:客户端的绘图API,能够在surface上面绘制图形;能够创建图形上下文;能够同步客户端绘图API和本地平台渲染API。
这使得OpenGL ES和OpenVG 在高性能,加速,2D和3D混合渲染上,能够无缝处理。

EGL的使用步骤是什么

  1. 众所周知,要绘制就得知道绘制到什么什么屏幕上。故第一步就是指定需要绘制的屏幕
eglGetDisplay(EGL_DEFAULT_DISPLAY);
返回的是display
  1. 在知道display之后,就要对其进行初始化了
eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor);
major,minor为返回的EGL版本
  1. 在知道显示屏幕之后,我们需要知道显示屏支持哪些格式,比如像素的格式是什么样子的,有没有哪些扩展格式,等
eglChooseConfig (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);
attrib_list是需要查询的一些属性
config为查询到的配置
一般情况下,会选择config中的第一个,后文代码中会提及,注意查看
  1. 知道了display,知道了版本,也知道了config。接下来就是使用这些信息,绘制需要的上下文
eglCreateContext (EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list);
唉,这里就是关键了
每次绘制都需要一个绘制上下文,这个上下文,保存绘制相关的一些状态。且上下文在各个线程中是不共享的。为了共享,可传递给第三个参数。
  1. 接下来就是创建,绘制区域了,绘制api将图形绘制在该区域上
 eglCreateWindowSurface (EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list);
 该函数表示,创建一个能够显示在Window系统中的绘制区域(Surface)
 对应的window由EGLNativeWindowType代表
  1. 创建了上下文,也创建了绘制区域。接下来就是将当前线程和上下文进行绑定
eglMakeCurrent (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
该函数将当前线程和绘制上下文进行绑定,并绑定draw和read surface
  1. 一切准备就绪,我们使用skia api进行绘制,放在后面介绍

  2. 绘制完成之后,将上面创建的surface,context,display分别销毁

eglDestroySurface (EGLDisplay dpy, EGLSurface surface);
销毁surface
eglDestroyContext (EGLDisplay dpy, EGLContext ctx);
销毁context
eglTerminate (EGLDisplay dpy);
销毁display

EGL如何在NDK中使用

上面说明了EGL的大致流程,接下来,看看如何在NDK中使用它。
首先创建一个SurfaceView.并为该SurfaceView设置一个回调监听,即SurfaceHolder.Callback.如下:

private SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(SurfaceHolder surfaceHolder) {
            native_surfaceCreate(surfaceHolder.getSurface());
            Timber.v("surfaceCreated");
        @Override
        public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int width, int height) {
            native_surfaceChange(width, height);
            Timber.v("surfaceChanged");
        @Override
        public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
            Timber.v("surfaceDestroyed");
            native_surfaceDestroy();

native_surfaceCreated,将会在c++ 层面创建一个绘制线程,在此不表,不过可以使用的api较多,如c++的std::thread,或者pthread,或者android独有的Android::Thread(只有系统编程人员可用)。在线程创建初期,进行EGL的初始化如下:

//下面的代码,严格按照上面EGL的步骤而设置
try {
        getDisplay();
        initilize();
        chooseConfig();
        createContext();
        lostContext = false;
    catch (const std::runtime_error error) {
        ALOGE("error %s", error.what());
        throw error;

初始化完成之后,接下来将SurfaceView创建的surface,传递给NDK中使用。

注意注意:前面说过SurfaceHolder.Callback的回调surfaceCreated,就已经创建好了surface,那么应该怎么传递给EGL使用呢?
这里就不得不说到,android的SurfaceFlinger和SurfaceView了。
1.SurfaceFlinger提供了一种机制,让别人能够调用SurfaceFlinger,创建一种图形产生和消耗的数据结构BufferQueue.这种机制之一叫做SurfaceControl.
2. SurfaceView使用SurfaceControl,让SurfaceFlinger产生一个BufferQueue.这个BufferQueue一头是SurfaceFlinger(消耗方).一头是SurfaceView(生产方).  这个BufferQueue创建出来的绘制区域都可以称为Surface。
3. 因此SurfaceHolder.Callback中的回调surfaceCreated,表示SurfaceView(生产方)使用的绘制区域已经创建完成。
4. 这个绘制区域在C层的实现就是使用的EGLNativeWindowType。因此调用eglCreateWindowSurface,就能创建对应的能在NDK中使用的绘制区域的代理对象了
从surface的java对象中获取,EGLNativeWindowType对象,如下:
JNIEXPORT void  JNICALL
Java_cn_xxxx_native_surfaceCreate(JNIEnv *env,jobject thiz,jobject surface) {
    ANativeWindow *window = ANativeWindow_fromSurface(env, surface);
    if (window == nullptr) {
        ALOGE("can't get native window from surface object");
        return;
    if (window != nullptr) {
        ANativeWindow_release(window);
        window = nullptr;
其中ANativeWindow就是EGLNativeWindowType
故下面的代码直接创建EGLSurface
void xxxEgl::createSurface(EGLNativeWindowType win) {
    EGLint attr_none[] = {EGL_NONE};
    eglSurface = eglCreateWindowSurface(display, firstConfig, win, attr_none);
    if (eglSurface == NULL && eglGetError() != EGL_SUCCESS) {
        wrapMessageOrException(string("create surface error reason:") + to_string(eglGetError()));
    if (eglMakeCurrent(display, eglSurface, eglSurface, context) == EGL_FALSE ||
        eglGetError() != EGL_SUCCESS) {
        wrapMessageOrException(
                string("create new surface context error reason:") + to_string(eglGetError()));

接下来就是native_surfaceChange了。在这里面,将初始化skia库,并进行绘制我们想要的图片。

初始化skia的步骤如下

void xxxSkia::initEnvironment(int32_t width, int32_t height) {
	//GrGLCreateNativeInterface:返回与当前操作系统相关的OpenGL接口
	//使用GrGLInterface接管,返回的接口。后续使用通过GrGLInterface进行
    sk_sp<const GrGLInterface> interface(GrGLCreateNativeInterface());
    //接口不能为空
    SkASSERT(NULL != interface);
    //使用上面获取的GrGLInterface来创建并初始化一个图形上下文GrContext
    sk_sp<GrContext> grContext(
            GrContext::Create(kOpenGL_GrBackend, (GrBackendContext) interface.get()));//后端使用OPENGLES
    //图形上下文不能为空
    SkASSERT(grContext);
    //GR_GL_GetIntegerv获取interface对应的GR_GL_FRAMEBUFFER_BINDING参数的值,并保存在buffer中
    GrGLint buffer;
    GR_GL_GetIntegerv(interface.get(), GR_GL_FRAMEBUFFER_BINDING, &buffer);
    //创建后端渲染目标对象GrBackendRenderTarget,创建的时候,指定宽,高,采样数(默认是0),
    //模版缓冲区位数(默认是8),以及像素格式。
    //其中info就是关联的OpenGL帧缓冲信息。
    GrGLFramebufferInfo info;
    info.fFBOID = (GrGLuint) buffer;
    GrBackendRenderTarget target(width, height, sampleCount, stencilBit,
                                 kSkia8888_GrPixelConfig, info);
    //SkSurfaceProps 是 Skia 中用于描述绘图表面属性的类。它可以影响绘图表面的渲染行为和性能。
    //这里表示使用:传统字体托管(font host)相关的方式。
    SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
    //有了后端渲染目标对象之后,就创建与之关联的绘制表面SkSurface.
    surface = SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
                                                     kBottomLeft_GrSurfaceOrigin,
                                                     nullptr, &props).release();
    mSurfaceHeight = height;
    mSurfaceWidth = width;
    //获取与surface相关的canvas,并在上面进行绘制。
    canvas = surface->getCanvas();

我们将使用skia的gpu后端,因此我们使用SkSurface::MakeFromBackendRenderTarget来创建对应的SkSurface对象。

在创建SkSurface对象时,需要传递,上下文接口,即grContext对象代表的逻辑。还需要传递目标绘制区域的一些信息,即target对象代表的逻辑。

创建完成之后,就可以使用skia的api进行绘制了。

那么需要在native_surfaceDestroy中,销毁必要的对象,除了EGL提及的,surface,context,display需要销毁以外,还需要销毁skia库中对应的SkSurface对象。请注意,请依照后创建先销毁的顺序,进行销毁

特别要注意的一点是:skia库,使用GPU后端进行渲染时。skia库可能创建一个gpu渲染上下文,因此有必要在绘制的地方,进行线程同步。比如,在我的例子中,进行了如下的同步:

void xxxSkia::draw() {
    //注意此处的锁
    std::lock_guard<std::mutex> _l(mutex);
    ATRACE_CALL();
    //一串字符串
    canvas->drawColor(SK_ColorWHITE);
    SkPaint paint;
    paint.setStyle(SkPaint::kFill_Style);
    paint.setAntiAlias(true);
    paint.setStrokeWidth(40);
    paint.setTextSize(40);
    paint.setColor(0xfffe938c);
    canvas->drawString("please set view", 100, 100, paint);
    ALOGD("this %p,context %p,surface %p",this,eglGetCurrentContext(),eglGetCurrentSurface(EGL_DRAW));
    canvas->flush();

使用上面的例子,成功将javacanvas的绘制,传递到了ndk中实现。附上一张实现结果图:
在这里插入图片描述

Android NDK-EGL 初级在最近的工作中,发现很少有资料直接介绍android EGL的。在翻越GLSurfaceView和Skia的源码之后,将我自己的NDK-EGL编程整理如下,供有需要的开发者取用什么是EGLEGL at a glanceEGL provides mechanisms for creating rendering surfaces onto which client APIs like OpenGL ES and OpenVG can draw, creates g 本文介绍在Android中与图像架构相关的概念或类Surface、SurfaceHolder、EGLSurface、SurfaceView、GLSurfaceView、SurfaceTexture、TextureView、SurfaceFlinger 和 Vulkan 的知识。 本页将介绍 Android 系统级图形架构的基本元素,以及应用框架和多媒体系统如何使用这些元素。我们会重点介绍图形数据的缓冲区是如何在系统中移动的。 如果您想了解 SurfaceView 和 TextureView . EGL :是渲染 API(如 OpenGL ES)和原生窗口系统之间的接口, 与 opengl 对接操作GPU 能力,android使用的是 openGL ES EGL10 11 14 OpenGL:是一个操作 GPU 的 API,它通过驱动向 GPU 发送相关指令OpenGL 是一个操作 GPU 的 API,它通过驱动向 GPU 发送相关指令,控制图形渲染管线状态机的运行状态,但是当涉及到与本地窗口系... 指定显示连接,默认连接为EGL_DEFAULT_DISPLAY EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); 打开连接之后,需要初始化EGL major 指定EGL实现返回的主版本号,可能为NULL minor 指定EGL实现返回的次版本号,可能为NULL EGLint major, minor; if (!eglInitialize(display, &major, &minor)){ OpenGl是一套跨平台的接口,它与各个平台本地窗口系统之间的交互,是借助于一个中间控制层,这个中间控制层就是EGL。EGL也有自己的一套标准API,由各个平台的系统来完成其具体实现。 EGL是OpenGL和本地窗口体系进行联系的桥梁,负责管理OpenGL的运行状态、渲染图像到本地窗口或缓冲区等功能。 在Android中,OpenGL的每一步处理,都需要依赖于EGL提供的这些相关功能支持,所以必须先创建EGL环境,才能正常进行OpenGL处理...
./android-ndk-r25c/sources/android/native_app_glue/Android.mk ./android-ndk-r25c/sources/android/support/Android.mk ./android-ndk-r25c/sources/android/ndk_helper/Android.mk ./android-ndk-r25c/sources/android/cpufeatures/Android.mk ./android-ndk-r25c/sources/cxx-stl/llvm-libc++abi/Android.mk ./android-ndk-r25c/sources/cxx-stl/llvm-libc++/Android.mk ./android-ndk-r25c/sources/third_party/googletest/Android.mk ./android-ndk-r25c/sources/third_party/shaderc/libshaderc_util/Android.mk ./android-ndk-r25c/sources/third_party/shaderc/third_party/glslang/Android.mk ./android-ndk-r25c/sources/third_party/shaderc/third_party/spirv-tools/Android.mk ./android-ndk-r25c/sources/third_party/shaderc/third_party/Android.mk ./android-ndk-r25c/sources/third_party/shaderc/libshaderc/Android.mk ./android-ndk-r25c/sources/third_party/shaderc/Android.mk ./android-ndk-r25c/sources/third_party/vulkan/src/build-android/jni/Android.mk哪个是NDK编译的mk
这些都是 Android NDK 内部的 `Android.mk` 文件。其中,`./android-ndk-r25c/sources/android/native_app_glue/Android.mk` 是用于编译 Native Activity 示例应用程序的 `Android.mk` 文件;`./android-ndk-r25c/sources/android/support/Android.mk` 是包含一些 Android 支持库的 `Android.mk` 文件;`./android-ndk-r25c/sources/android/ndk_helper/Android.mk` 是包含一些辅助函数和类的 `Android.mk` 文件;`./android-ndk-r25c/sources/android/cpufeatures/Android.mk` 是用于编译 `cpufeatures` 库的 `Android.mk` 文件,该库提供了一些 CPU 相关的信息和功能;`./android-ndk-r25c/sources/cxx-stl/llvm-libc++abi/Android.mk` 和 `./android-ndk-r25c/sources/cxx-stl/llvm-libc++/Android.mk` 是用于编译 C++ STL 库的 `Android.mk` 文件,分别对应 libc++abi 和 libc++ 两个 STL 库;`./android-ndk-r25c/sources/third_party/googletest/Android.mk` 是用于编译 Google Test 测试框架的 `Android.mk` 文件;`./android-ndk-r25c/sources/third_party/shaderc/Android.mk` 是用于编译 Shaderc 编译器的 `Android.mk` 文件,该编译器可以将 GLSL 代码编译成 SPIR-V 代码;`./android-ndk-r25c/sources/third_party/shaderc/libshaderc/Android.mk` 是用于编译 Shaderc 库的 `Android.mk` 文件;`./android-ndk-r25c/sources/third_party/shaderc/libshaderc_util/Android.mk` 是用于编译 Shaderc Util 库的 `Android.mk` 文件,该库提供了一些辅助函数和类;`./android-ndk-r25c/sources/third_party/shaderc/third_party/Android.mk` 是用于编译 Shaderc 编译器依赖的第三方库的 `Android.mk` 文件,包括 glslang 和 spirv-tools 两个库;`./android-ndk-r25c/sources/third_party/shaderc/third_party/glslang/Android.mk` 是用于编译 glslang 库的 `Android.mk` 文件;`./android-ndk-r25c/sources/third_party/shaderc/third_party/spirv-tools/Android.mk` 是用于编译 spirv-tools 库的 `Android.mk` 文件;`./android-ndk-r25c/sources/third_party/vulkan/src/build-android/jni/Android.mk` 是用于编译 Vulkan 库的 `Android.mk` 文件。 如果您要在 Android NDK 中编写自己的 `Android.mk` 文件,可以参考这些示例文件。