OpenGL ES上下文环境搭建

news2025/2/23 23:04:20

由于 OpenGL ES 一开始就是为跨平台设计的,所以它本身并不承担窗口管理以及上下文环境构建的职责,这个职责需要由各自的平台来承担。

Android 平台使用的是 EGL,EGL 是 Khronos 创建的一个框架,用来给 OpenGL 的输出与设备的屏幕搭建起一个桥梁。EGL 的工作机制是双缓冲模式,也就是有一个 Back Frame Buffer 和一个 Front Frame Buffer,正常绘制操作的目标都是 Back Frame Buffer,渲染完毕之后,调用 eglSwapBuffer 这个 API,会将绘制完毕的 Back Frame Buffer 与当前的 Front Frame Buffer 进行交换,然后显示出来。EGL 承担了为 OpenGL 提供上下文环境以及窗口管理的职责。

iOS 平台使用的是 EAGL,与 EGL 的双缓冲机制比较类似,iOS 平台也不允许我们直接渲染到屏幕上,而是渲染到一个叫 renderBuffer 的对象上。这个对象你可以理解为一个特殊的 FrameBuffer,最终再调用 EAGLContext 的 presentRenderBuffer 方法,就可以将渲染结果输出到屏幕上去了。

Android 平台的环境搭建

要在 Android 平台上使用 OpenGL ES,最简单的方式是使用 GLSurfaceView,因为不需要开发者搭建 OpenGL ES 的上下文环境以及创建 OpenGL ES 的显示设备,只需要遵循 GLSurfaceView 定义的接口,实现对应的逻辑即可。就像硬币有正反面一样,使用 GLSurfaceView 的缺点也比较明显,就是不够灵活,OpenGL ES 很多核心用法,比如共享上下文,使用起来就会比较麻烦。
此处会直接使用 EGL 提供的 API,在 Native 层基于 C++ 环境进行搭建。原因是在 Java 层进行搭建的话,对于普通的应用也许可以,但是对于要进行解码或使用第三方库的场景,比如人脸识别,又需要到 Native 层来实施。考虑到效率和性能,我们这里就直接使用 Native 层的 EGL 来搭建一个 OpenGL ES 的开发环境。

引入头文件与 so 库

我们必须要在 CMake 构建脚本(CMakeLists.txt)中加入 EGL 这个库,并在使用这个库的 C++ 文件中引入 EGL 对应的头文件。
需要引用的头文件如下:

#include <EGL/egl.h>
#include <EGL/eglext.h>

需要引入的 so 库:

target_link_libraries(videoengine
        # 引入系统的动态库
        EGL
        )

这样我们就可以在 Android 的 Native 层中使用 EGL 了,不过要使用 OpenGL ES 给开发者提供的接口,还需要引入 OpenGL ES 对应的头文件与库。
需要包含的头文件:

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

需要引入的 so 库,注意这里使用的是 OpenGL ES 2.0 版本。

target_link_libraries(videoengine
        # 引入系统的动态库
        GLESv2
        )

至此,OpenGL ES 的开发需要用到的头文件以及库文件就引入完毕了,下面我们来看一下如何使用 EGL 搭建出 OpenGL 的上下文环境以及实现窗口的管理。

EGLDisplay 作为绘制的目标

EGL 首先要解决的问题是,要告诉 OpenGL ES 绘制的目标在哪里,而 EGLDisplay 就是一个封装系统物理屏幕的数据类型,也就是绘制目标的一个抽象。开发者要调用 eglGetDisplay 这个方法来创建出 EGLDisplay 的对象,在调用这个方法的传参中,常量 EGL_DEFAULT_DISPLAY 会被传进这个方法中,每个厂商在自己的实现中都会返回默认的显示设备,代码如下:

EGLDisplay display;
if ((display = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY) {
    LOGE("eglGetDisplay() returned error %d", eglGetError());
    return false;
}

在获得了 EGLDisplay 的对象之后,就需要对这个对象做初始化工作,开发者需要调用 EGL 提供的 eglInitialize 方法来初始化这个对象,这个方法会返回一个 bool 值来代表函数运行结果。函数的第一个参数就是 EGLDisplay 对象,后面两个参数是这个函数为了返回 EGL 版本号而设计的,两个参数分别是 Major 和 Minor 的 Version,比如 EGL 的版本号是 1.0,那么 Major 返回 1,Minor 则返回 0,如果我们不关心版本号,可以都传入 0 或者 NULL,代码如下:

if (!eglInitialize(display, 0, 0)) {
    LOGE("eglInitialize() returned error %d", eglGetError());
    return false;
}

一旦 EGLDisplay 初始化成功之后,它就可以将 OpenGL ES 的输出和设备的屏幕桥接起来,但是需要我们指定一些配置项,比如色彩格式、像素格式、OpenGL 版本以及 SurfaceType 等,不同的系统以及平台使用的 EGL 标准是不同的,在 Android 平台下一般配置的代码如下所示:

EGLConfig config;
const EGLint attribs[] = {EGL_BUFFER_SIZE, 32,
        EGL_ALPHA_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_RED_SIZE, 8,    
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_NONE };
if (!eglChooseConfig(display, attribs, &config, 1, &numConfigs)) {
    LOGE("eglChooseConfig() returned error %d", eglGetError());
    return false;
}

配置选项这个函数也是返回一个 bool 值来代表配置状态,如果配置成功,则代表 config 会被正确初始化。
EGLDisplay 这个对象是 EGL 给开发者提供的最重要的入口,接下来我们要基于这个 EGLDisplay 对象还有 EGLConfig 配置来创建上下文了。

EGLContext 提供线程的上下文

由于任何一条 OpenGL ES 指令(OpenGL ES 提供给开发者的接口)都必须运行在自己的 OpenGL 上下文环境中,EGL 提供 EGLContext 来封装上下文,可以按照如下代码构建出 OpenGL ES 的上下文环境。

EGLContext context;
EGLint attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
if (!(context = eglCreateContext(display, config, NULL,
        eglContextAttributes))) {
    LOGE("eglCreateContext() returned error %d", eglGetError());
    return false;
}

函数 eglCreateContext 的前两个参数就是我们刚刚创建的两个对象,第三个参数类型也是 EGLContext 类型的,一般会有两种用法。

  • 如果想和已经存在的某个上下文共享 OpenGL 资源(包括纹理 ID、frameBuffer 以及其他的 Buffer),则传入对应的那个上下文变量。
  • 如果目标仅仅是创建一个独立的上下文,不需要和其他 OpenGL ES 的上下文共享任何资源,则设置为 NULL。
    在一些场景下其实需要多个线程共同执行 OpenGL ES 的渲染操作,这种情况下就需要用到共享上下文,共享上下文的关键点就在这里,一定要记住。

成功创建出 OpenGL ES 的上下文,说明我们已经把 OpenGL ES 的绘制目标搞定了,但是这个绘制目标并没有渲染到我们某个 View 上,那如何将这个输出渲染到业务指定的 View 上呢?答案就在 EGLSurface。

EGLSurface 将 EGLDisplay 与系统屏幕桥接起来

EGLSurface 实际上是一个 FrameBuffer,开发者可以调用 EGL 提供的 eglCreateWindowSurface 创建一个可实际显示的 Surface,调用 eglCreatePbufferSuface 可以创建一个 OffScreen(离屏渲染,一般用户后台保存场景)的 Surface,创建可实际显示的 Surface 代码如下:

EGLSurface surface = NULL;
EGLint format;
if (!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID,
        &format)) {
    LOGE("eglGetConfigAttrib() returned error %d", eglGetError());
    return surface;
}
ANativeWindow_setBuffersGeometry(_window, 0, 0, format);
if (!(surface = eglCreateWindowSurface(display, config, _window, 0))) {
    LOGE("eglCreateWindowSurface() returned error %d", eglGetError());
}

上述代码中不得不提的就是 _window 这个参数,这里我需要重点向你解释一下,这个 _window 是 ANativeWindow 类型的对象,代表了本地业务层想要绘制到的目标 View。在 Android 里面可以通过 Surface(通过 SurfaceView 或 TextureView 来得到或者构建出 Surface 对象)去构建出 ANativeWindow。但构建之前需要我们在使用的时候引用头文件。

#include <android/native_window.h>
#include <android/native_window_jni.h>

调用 ANAtiveWindow 的 API 接口如下:

ANativeWindow* window = ANativeWindow_fromSurface(env, surface);

里面的 env 是 JNI 层的 JNIEnv 指针类型的变量,surface 就是 jobject 类型的变量,是由 Java 层的 Surface 类型对象传递而来的。到这里我们就把 EGLSurface 和 Java 层的 View(即设备的屏幕)连接起来了。
这里我们再补充一点,如果想做离屏渲染,也就是在后台使用 OpenGL 处理一些图像,就需要用到处理图像的 Surface 了,创建离屏 Surface 如下:

EGLSurface surface;
EGLint PbufferAttributes[] = { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE,
        EGL_NONE };
if (!(surface = eglCreatePbufferSurface(display, config, PbufferAttributes))) {
    LOGE("eglCreatePbufferSurface() returned error %d", eglGetError());
}

可以看到这个 Surface 并不会和任何一个业务 View 进行关联,进行离屏渲染的时候,就可以把这个 Surface 当作目标进行绘制。
在这里插入图片描述
现在我们已经用 EGL 准备好了上下文环境与窗口管理,如上图所示,左侧我们为 OpenGL ES 准备好上下文环境,并且用 EGLDisplay 用来接收绘制的内容,右边通过 EGLSurface 连接好了设备的屏幕(Java 层提供的 SurfaceView 或者 TextureView)。

为绘制线程绑定上下文

OpenGL ES 需要开发者自己开辟一个新的线程,来执行 OpenGL ES 的渲染操作,还要求开发者在执行渲染操作前要为这个线程绑定上下文环境。EGL 为绑定上下文环境提供了 eglMakeCurrent 这个接口。

eglMakeCurrent(display, eglSurface, eglSurface, context);

在绑定了上下文环境以及窗口之后就可以执行 RenderLoop 循环了,每一次循环都是去调用 OpenGL ES 指令绘制图像。
还记得我们之前提到的 EGL 的双缓冲模式吗?EGL 内部有两个帧缓冲区(frameBuffer),我们执行的渲染目标都是后台的 frameBuffer。当渲染操作完成之后,要调用函数 eglSwapBuffers 进行前台 frameBuffer 和后台 frameBuffer 的交换。

eglSwapBuffers(display, eglSurface)

执行上述函数之后,用户就可以在屏幕上看到刚刚渲染的图像了。

销毁资源

最后执行完所有的绘制操作之后,需要销毁资源。注意销毁资源也必须在这个独立的线程中,销毁显示设备(EGLSurface)的代码如下:

eglDestroySurface(display, eglSurface);

销毁上下文(Context)代码如下:

eglDestroyContext(display, context);

到这儿,在 Android 平台的 Native 层中,我们就使用 EGL 成功地把 OpenGL ES 的上下文环境搭建出来了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/362237.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手撸React组件库前必须清楚的9个问题

1. 组件库文档问题 以前常用的组件库文档storybook&#xff0c;包括现在也有用dumi、vitepress做组件库文档等。storybook缺点不美观、webpack的热更新太慢&#xff0c;虽然新版本支持了vite提高了速度但还不算稳定。好在各种文档、mdx、测试等组件第三方工具很多集成进去能很…

day37 动态规划 | 738、单调递增的数字 714、买卖股票的最佳时机含手续费 968、监控二叉树

题目 738、单调递增的数字 给定一个非负整数 N&#xff0c;找出小于或等于 N 的最大的整数&#xff0c;同时这个整数需要满足其各个位数上的数字是单调递增。 &#xff08;当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。&a…

什么是项目沟通管理? 借助系统软件管理项目沟通

在如今竞争激烈的市场环境中&#xff0c;很多企业内部往往有多个项目同时进行着&#xff0c;不同类别的项目需要项目负责人实时跟进&#xff0c;而成功的项目是离不开项目人员之间的日常互相沟通进行。只有良好的沟通&#xff0c;项目经理才能够获取到足够的信息&#xff0c;可…

国产真无线蓝牙耳机哪个好?国产半入耳蓝牙耳机推荐

近几年&#xff0c;生活中随处可见的有戴蓝牙耳机的人&#xff0c;而蓝牙耳机也因为使用更便捷、功能更先进受到了不少用户的喜爱。蓝牙耳机按照佩戴方式来划分&#xff0c;可以有入耳式、半入耳式、头戴式等。在此&#xff0c;我来给大家推荐几款国产半入耳蓝牙耳机&#xff0…

LeetCode 606.根据二叉树创建字符串,102.二叉树的层序遍历和牛客 二叉搜索树与双向链表

文章目录1. 根据二叉树创建字符串2. 二叉树的层序遍历3. 二叉搜索树与双向链表1. 根据二叉树创建字符串 难度 简单 题目链接 解题思路&#xff1a; 这里的意思就是&#xff1a;用前序遍历遍历这颗树。然后左子树和右子树分别在一个括号里。括号里的规则是&#xff1a; 1.左右都…

W800|WIFI|CDK|W80X SDK v1.00.10|官方demo|学习(2):t-connect

W800 SDK代码及相关文档获取地址&#xff1a; https://www.winnermicro.com/html/1/156/158/558.html 1、W800 SDK v1.00.10更新内容&#xff1a; 1. 驱动更新 1&#xff09;提供模组ADC校准功能接口 2&#xff09;修复PSRAM IO复用不完整问题 3&#xff09;Flash驱动修改不再…

CSDN每日一练非降序数组 C语言/C++

题目名称&#xff1a;非降序数组 时间限制&#xff1a;1000ms内存限制&#xff1a;256M 题目描述 写一个函数&#xff0c;传入两个非降序的整数数组&#xff08;A, B&#xff09;&#xff0c;将 A, B 合并成一个非降序数组 C&#xff0c;返回 C&#xff08;不要使用内置 sort 函…

新项目分析

1&#xff1a;数据类型处理 # sep‘\s‘ 这是正则表达式&#xff0c;通过一定规则的表达式来匹配字符串用的 \s 表示空白字符&#xff0c;包括但不限于空格、回车(\r)、换行(\n)、tab或者叫水平制表符(\t)等&#xff0c;这个根据编码格式不同代表的含义也不一样&#xff0c;感…

一文讲解系统调用与函数调用有什么区别?

作为程序员你肯定写过无数的函数&#xff0c;假设有这样两个函数&#xff1a; void funcB() { }void funcA() {funcB(); } 函数之间是可以相互调用的&#xff0c;这很简单很happy有没有。 要知道是代码、是函数就可以相互调用&#xff0c;不管你用什么语言写的。 假设funcB…

2023/02/21 事件循环-eventloop 宏任务 微任务 讲解

1 JS是单线程 js是单线程的。也就是说&#xff0c;同一个时间只能做一件事。作为浏览器脚本语言&#xff0c;与它的用途有关。JavaScript的主要用途是和用户互动&#xff0c;以及操作DOM&#xff0c;这决定了它只能是单线程。 js是单线程的。也就是说&#xff0c;同一个时间只…

如何使用 API 工具做 Websocket 测试

在 API 测试中&#xff0c;对 Websocket 协议的支持呼声越来越高&#xff0c;今天给大家推荐一款 开源的 API 管理工具——Postcat&#xff0c;以及教教大家&#xff0c;如何利用 API 管理工具做 Websocket 测试。 在线 Demo 链接&#xff1a;Postcat - Open Source API Ecosys…

17 个短代码,检验你 Python 基本功

Python 是一门非常优美的语言&#xff0c;其简洁易用令人不得不感概人生苦短。在本文中&#xff0c;蛋糕将带大家回顾 17个非常有用的 Python 技巧&#xff0c;例如查找、分割和合并列表等。这 17 个技巧都非常简单&#xff0c;但它们都很常用且能激发不一样的思路。 人生苦短&…

来一波骚操作,Java内存模型

文章整理自 博学谷狂野架构师 什么是JMM 并发编程领域的关键问题 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息。在编程中&#xff0c;线程之间的通信机制有两种&#xff0c;共享内存和消息传递。 ​ 在共享内存的并发模型里&#xff0c;线程之间共享程序的公共…

项目管理从需求管理开始--不懂需求管理还敢带项目?

分析报告指出&#xff0c;多达76%的项目失败是因为差劲的需求管理&#xff0c;这个是项目失败的最主要原因&#xff0c;比技术、进度失控或者混乱的变更管理还要关键。很多PMO和PM却没有把需求管理重视起来&#xff0c;甚至认为这只是产品经理的事情&#xff0c;自己只做交付即…

Spark RDD及内存计算

文章目录Spark RDD及内存计算性能调优RDD 的核心特征和属性内存计算Spark RDD及内存计算 性能调优 性能调优的本质&#xff1a; 性能调优不是一锤子买卖&#xff0c;补齐一个短板&#xff0c;其他板子可能会成为新的短板。因此&#xff0c;它是 一个动态、持续不断的过程&…

第51篇-某彩网登录参数分析-webpack【2023-02-21】

声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、网站分析一、前言 今天我们看一个webpack的网站 aHR0cHM6Ly8xMGNhaTUwMC5jYy9sb2dpbg==二、网站分析 首先…

Springboot 全局异常处理类

全局异常处理 在开发过程中&#xff0c;不管是Dao、Servie、Controller,层都有可能发生异常&#xff0c;对于异常处理&#xff0c;通常是try&#xff0d;catch或者直接throw&#xff0c;这会让try&#xff0d;catch的代码在代码中任意出现&#xff0c;系统的代码耦合度高&…

Elasticsearch7.8.0版本进阶——数据更新流程

目录一、数据更新流程概述二、数据更新流程步骤2.1、数据更新流程图解2.2、部分更新一个文档的步骤2.3、数据更新流程注意事项一、数据更新流程概述 部分更新一个文档需要结合数据读取和写入流程。 二、数据更新流程步骤 2.1、数据更新流程图解 2.2、部分更新一个文档的步骤…

经典文献阅读之--MSC-VO(曼哈顿和结构约束VIO)

0. 简介 对于视觉里程计而言&#xff0c;在面对低纹理场景时&#xff0c;往往会出现退化的问题&#xff0c;究其原因是人造环境往往很难找到足够数量的点特征。而其他的几何视觉线索则是比较容易找到&#xff0c;在城市等场景中&#xff0c;通常表现出结构规律&#xff0c;如平…

程序中的日期使用问题-格式转化:SimpleDateFormat、org.apache.commons.lang3.time.DateUtils

前言 日期使用问题主要是格式转换的问题 场景&#xff1a;通过excel导入数据&#xff0c;其中一个字段为出生日期&#xff0c;需要对字段值进行合法性校验 博客地址&#xff1a;芒果橙的个人博客 【http://mangocheng.com】 一、个人浅谈日期 时间日期作为一个基础的标识和维度…