每天一点点_OpenGL_GLES实现

GLES 和 EGL

OpenGL 是一个跨平台的图形 API, 它为 3D 处理硬件声明了一套软件接口。 OpenGL ES 是 OpenGL 的一个子集, 专门用于嵌入式设备。OpenGL ES 缩写为 GLES。

Android 设备需要提供 EGL, GLES 1.x, GLES 2.0 的驱动。对 GLES 3.x 的支持是可选的。关键的注意事项包括:

  • 确保 GL 驱动是健壮的,符合 OpenGL ES 标准
  • 允许无限数量的 GL Context。因为 Android 允许应用在后台保持 GL Context 活着。所以不应该在驱动里限制 GL Context 的数量。
  • 注意每个 GL Context 分配的内存, 因为经常会同时有 20-30 个 GL Context
  • 支持 YV12 图像格式和其他 YUV 图像格式。他们来自系统的其他组件。比如来自 MediaCodec 或者 Camera
  • 支持强制的扩展: EGL_wait_sync, GL_OES_texture_external, EGL_ANDROID_image_native_buffer 和 EGL_ANDROID_recordable。另外, 硬件合成器1.1版本或更高版本要求支持 EGL_ANDROID_framebuffer_target 扩展

支持 EGL_ANDROID_blob_cache, EGL_KHR_fence_sync, 和 EGL_ANDROID_native_fence_sync 也是被强烈推荐去实现的。

以上的扩展,只用过 GL_OES_texture_external, 它的使用情景是,当我们使用 texture 接收了系统的 SurfaceTexture 来的图像数据。

每天一点点_OpenGL_EGLSurface

前面说到 EGLSurface 分两类:

  • 一类是绘制到屏幕上的,在 Android 上, 与系统的 Surface 连接,作为图像的生产着,Surface 内部的 BufferQueue 另一端连接消费者,比如 SurfaceFlinger 负责显示图像到屏幕, 编码器负责将图像编码等。

  • 另一来绘制图像数据到有 EGL 实现的图像缓冲区, 是一个离屏的缓冲区。(这里还没接触过, 用第一种,也可以不显示出来,直接编码进文件, 不知道离屏是不是这个意思)

EGL 没有提供 lock/unlock 操作。 在执行了绘制操作后,执行 eglSwapBuffers() 提交当前帧。这个方法的名字来自传统的交换前段和后端的缓冲区。但是实际实现可能不同。这里的前段和后端的缓冲区,指的是双缓冲, 为了提高效率,在提交到前段缓冲区后,缓冲区就被消费者去使用了,在这同时,绘制后端缓冲区。

在同一时间,只有一个 EGLSurface 可以连接到 Surface(也就是说只有一个生产者连接到 BufferQueue),但是如果销毁了 EGLSurface, BufferQueuer 就可以在连接其他的生产者了。

一个线程可以在多个EGLSurface之间切换, 切换的方式是通过 makeCurrent 设置当前的 EGLSurface。一个 EGLSurface 在某个时间只能在一个线程作为当前的EGLSurface。这也好理解,如果同时被多个线程设置为当前的 EGLSurface, 那就可能同时多个线程输出图像数据到它上面。

EGL 不像 SurfaceHolder 是 Surface 的另一面。EGL 只是与之相关,但是独立的概念。EGL 可以不使用 Surface 实现。Surface 也可以在没有 EGL 情况下使用。EGLSurface 只是提供了一个地方让 GLES 绘制。

明天: 每天一点点_OpenGL_GLES实现

每天一点点_OpenGL_概述

Android 使用 OpenGL ES API 渲染图形。OpenGL ES 缩写为 GLES。 GLES 是 OpenGL 的适于嵌入式设备的一个版本。OpenGL 是一个渲染 API,只负责渲染。渲染到哪里呢?着要系统实现。

为了创建 GLES 上下文并为 GLES 提供窗口系统, Android 使用 EGL 库。EGL 就是负责提供一个渲染的地方, GLES 会绘制到它上面。

在使用 GLES 进行绘制之前, 需要创建 GL 上下文。 在 EGL 中, 这意味着要创建 EGLContext 和 EGLSurface。GLES 操作应用于当前的上下文, 当前的上下文使用 线程本地变量来存储而不是使用参数传递。渲染的代码应该在当前的 GLES 线程,而不能在 UI 线程。

EGLSurface

EGLSurface 有两种:

  • 一种就是显示到屏幕上的,是被系统分配的。使用 eglCreateWindowSurface()创建
  • 另一种是绘制到 离屏的缓冲区, 由 EGL 分配。 使用 eglCreatePbufferSurface() 创建

EGL 相当于 GLES 的画布, 将绘制的数据传出去。

每天一点点_frame_pacing

Frame Pacing Library 也叫 Swappy 是 Android Game SDK 的一部分。它帮助 OpenGL 和 Vulkan 在 Android 上实现游戏的顺滑渲染,纠正帧的步调节奏。

Frame Pacing 是 游戏逻辑 和 渲染循环的同步。渲染循环指的是系统的显示子系统和底层显示硬件的循环。Android 的显示子系统被设计避免屏幕内容撕裂等问题。

显示子系统如何避免内容的撕裂呢?

  1. 缓存之前的帧
  2. 检测是否有迟到的帧,所谓迟到指的是,因为有固定的帧率,每一帧有特定的的时间,如果晚于这个时间,就是迟到
  3. 当检测到迟到的帧时,显示当前的帧

这个逻辑很简单嘛

明天: 每天一点点_OpenGL_概述

论反复练习

有一天,我突然想明白了自己的矛盾:一边想安定,一边想创业。当时,我只记下了这句话,却没记下为什么,肯定是当时以为明白了,还用记吗?不幸的是,我先在忘记当时怎么想得了。

首先,这种当时觉得很明白,学会了,然后就觉得没必要记录了的想法实在是太具有杀伤力。比如我听了一堂课,那堂课挺难,但是我跟着老师的思路跑,感觉都明白,然后下课了,不去看了,然后,就忘了。比如看了一本书,觉得实在是太有道理了,就是我的想法,然后就不在去看了,然后,就忘了。

说回那个问题, 我现在的理解是这样的:想安定,是因为想在位置上定下来,自己不那么请装上阵了,外物太多了,笔记本,吉他,画板,还有其他很多东西,当然这些都不是根本原因,可能就是年龄到了,也在外面浪了一圈。想创业,首先,我觉的,创业与安定并不矛盾,尽管很多人不这么认为,还有,创业也并不意味这高风险,好吧,可能,这只是我的想法,我是想找个低风险的创业项目。

关于创业项目的选择,我还是一直想着去做一个应用,尽管我知道,创业很广泛,去摆个摊,去开个店,去开个抖音号发视频,但是我就是不想去,就是不想做其它的创业项目,可能也算是思路不开阔,格局打不开吧。

说会这篇文章的主题,我想说说反复。其实这个词经常出现在我的生活中,因为要想掌握一个东西必须要反复。为什么, 一方面,人类会遗忘, 一方面,一遍过去,你的吸收能力有限,就算都吸收了,那也是在你当时的认知上吸收了,比如看完了一遍,如果再看第二遍,第一页的内容,在给予书后面的内容上,会有更多的理解。

这篇文章的主题是反复练习,与高级的说法是刻意练习,带有目标对某一小块的反复练习,像极了初中,高中的专项训练。

在练习吉他的过程中,我发现,刻意练习真的太重要了,大横按,按不好,刻意练习它呀。但是刻意练习比较难,比较让人讨厌,怎么办?这其实是两个两个问题,刻意练习比较难怎么办?刻意练习比较讨厌怎么办?

对于第一个问题可以从两个方面着手解决:

  1. 分解成更小的部分
  2. 减慢速度

对于第二个问题可以从两方面着手解决:

  1. 变着花样来,目标不要变
  2. 目标清晰,即使反馈,有评分机制

可能,这些解决方法你早就知道了,又或者太抽象了,无法执行。我下面尝试以练习大横按为例子,提供一些练习方法,希望有所启发。

首先说明一下大横按是什么,大横按是在按吉他和弦时,食指需要按住6根弦的某一品,其他手指再按住某几个弦。具体的例子比如 F 和弦,如图:

这个动作对于初学者来说,需要手上的力量,食指同时按几根弦比按一根弦要用更多的力气。具体的技巧和分析我这里不会讲,只会说明练习策略。

这个动作很难,我们想办法让它简单点。

  1. 一下按住要按住的所有弦太难了,那么,我们一下按住一个总会简单点吧。所以,我们保持手形放在相应的位置,但是我们的检测目标不是一次弹响整个 F 和弦,而是只弹一根。
  2. 乐器它自带及时反馈,你按对了弦,弹的音自然是对的,这就是一个正反馈
  3. 弹完一个音,要放松手形,不然会很容易累。
  4. 等基本能按顺序弹弦了,可以打开节拍器,这样又加大了难度,不过增加了反馈,如果感觉比较难,就调慢节拍器,只要感觉难就调慢,感觉熟练了,就再加快点。
  5. 这样,增加难度上,一方面,可以提高速度,一方面可以增加手指,另外,因为现在是按的和弦,所以随便按弦,都不会太难听,甚至可以来个即兴solo。这个过程,应该是比较愉悦的。
  6. 过程如果愉悦了,就不会停止,就必然能达到目标。
  7. 体会,欣赏过程。

这个过程,将一个大的陡坡变成了缓坡。这就是微积分的思想吧。

每天一点点_VSYNC

VSYNC 好像跟上层的编程没关系, 它负责同步应用的渲染,SurfaceFlinger的合成,HWC输出图像数据到显示器。这有利于消除卡顿,提高图像的显示效果。

VSYNC 是 HWC 生成。回调给 SurfaceFlinger

SurfaceFlinger 控制 HWC 是否生成 VSYNC 事件。

SurfadceFlinger 启动 VSYNC 事件,这样它可以和显示器的刷新循环同步。当SurfaceFlinger 与显示器的刷新循环的同步了, SurfaceFlinger 又会停止 HWC 产生 VSYNC 信号。

如果 SurfaceFlinger 检测到了现在这个 VSYNC 和之前构建的 VSYNC 不同, SurfaceFlinger 会重新启动 VSYNC 生成。

明天: 每天一点点_frame_pacing

每天一点点音视频_layer_display

Layer 和 Display 是图像显示合成,与显示器硬件交互的两个基本元素。

Layer (层)

层 是最重要的合成单元。一个 层 包含了 一个 Surface 和 一个 SurafceControl。每一个 层 有一些列的属性来定义与其他层的交互。层的属性如下表:

属性 描述
位置相关的 定义了 层 在 Display 上的显示位置。包括四个边的位置, 和相对其他 层 的 z-order, 也就是 层 和 层 的层叠关系
内容相关的 定义了内容如何显示在上面位置定义的区域内。包括了裁切(内容拉伸到比上面定义的区域大了), 和变换(反转或者旋转)
合成相关的 定义了层和层如何合成, 包括融合的模式,alpyha 的合成
优化相关的 提供了一些不是必要的信息,但是会指导 HWC 去更高效的合成。包括了层的可见区域,和已经更新了的区域

这个部分的信息像极了现在正在做的视频编辑 sdk 的视频帧的绘制部分。实际上本来就是一样的。

Display (就译成显示器吧, 形象,具象, 但是这是个抽象概念,不是那个显示器)

这里的 显示器 是图像合成里的另一个重要的单元。一个系统可以有多个显示器,显示器可以被添加或者移除。显示器会根据系统或者 HWC 的需求而添加和删除。

HWC 会在显示器设备添加或移除的时候请求 显示器 添加或者删除,这叫热插拔。

客户端会请求虚拟显示器,虚拟现实器的内容会被渲染到一个离屏的缓冲区而不是物理的设备上。这就是这个抽象概念的作用,它不仅可以代表一个显示器设备,还可以代表一个缓冲区。

Virtual Display (虚拟显示器)

SurfaceFlinger 支持一个内部屏幕(比如手机和平板的屏幕),外部屏幕(比如通过 HDMI 连接的电视),和一个或多个虚拟显示器。虚拟现实器使得合成的图像可以被系统使用。虚拟显示器可以备用来录制屏幕或者或者把屏幕内容发送到网络上去。为虚拟现实器生成的帧会写入 BufferQueue

虚拟显示器可能会和主显示器共享一堆层,也可能有它们自己的。虚拟现实器没有 VSYNC 信号,这样内部显示器的 VSYNC 信号就用来出发所有显示器的合成。

当 HWC 支持虚拟现实器时, 虚拟现实器可能被 GLES 实现,也可能被 HWC 实现,或者被它两者一块实现。当 HWC 不支持虚拟现实器时,系统会使用 GLES 实现。

每天一点点音视频_HWC1

Android 对 HWC 实现要求支持:

  1. 至少能合成四个层: 状态栏, 系统栏(这个难道是通知下拉的那个), 应用, 背景
  2. 比显示屏大的层
  3. 下边几个严重朝纲了,以后在研究吧。

今天时间是花了,但是写不动了。关于 HAL, 关于硬件,实在太多不了解。但是还是要硬着头皮看。

不过明天的应该有趣。

明天: 每天一点点音视频_layer_display

纪念一次摄像头对焦功能完成

首先来个大大的

我草

最近实现相机触摸对焦功能, 用了一周吧, 功能实现2天,剩下的时间调准。

先说为什么条准这么费尽: 因为相机输出到显示画面,经过了多重变换。

然后, 对于屏幕上的一个触摸坐标,最终转换到相机的传感器上的坐标,要经过从下往上的一个逆过程。

我花了大量的时间做这些变换。但是却一直搞错了旋转变换。屏幕坐标转换到传感器坐标的时候需要顺时针转,我却一直做逆时针转。发现这个问题是我写了另一个 Demo 发现的。

为什么,要通过再写个 demo 才发现这个旋转的问题的, 这就是开头的大大的我草的原因所在。

我在代码里传给相机的焦点坐标给传错了,没有传转换后的坐标,而是屏幕上的坐标。这就是无论我怎么改转换的逻辑都没有效果,甚至,而我的打印信息时,打印了转换的函数输入输出,所以从打印信息来看正确。

怎么避免这种情况呢

我对这种问题还真没有信心来避免,或者说曾经是。因为在我的印象里,马虎,偶尔的小错误,是无法避免的。为什么说曾经是,因为我觉的我现在犯小错误少了,或者印象里少了,也就是说,犯了可能美在意,没以前那么在以了。

但是,还是要有一种方法论的观念,也就是说,凡事有方法的信念。

  1. 小心点。 尽管我知道,这不是小心的问题,肯定是是改来改去的原因,忘了改了哪里,用电路实验的说法,本来想短路了来解决问题,最后出厂了还短路着。

  2. 保持头脑清醒。这个也是白扯。清醒并不是想保持就能保持的。还是看自己的做的事情,尽量做少的变动,及时反馈。我有想起了 TDD, 写个失败的测试,通过,重构,循环。让一次循环越小,头脑就会越清醒。

  3. 提前把大的问题模型或者叫解决方案搞清楚,写出来,然后找出关键点,先解决关键点。

每天一点点音视频_HWC

HWC = Hardware Composer HAL

HWC 负责决定使用可用的硬件以最有效的方式合成图像缓冲区

HAL 时 Android 系统架构中的一层, Hardware Abstract Layer (硬件抽象层)。硬件抽象层的实现是设备特定的,被设备厂商实现。

当你考虑多个缓冲区的覆盖融合时, HWC 这种方式的价值就很容易理解了。比如一个 Android 手机竖屏时, 有顶部的状态栏,底部的导航条,和其余的部分, 在上层来说,时三个 Window, SurafceFlinger 来说是三个层, 可以有两种方法来处理:

  1. 渲染所有的层到另一个 缓冲区, 然后将最终的缓冲区给显示器硬件
  2. 把所有的层给显示器硬件, 它读取不同的部分合成显示出来

后一种当然会更有效,因为融合的工作交给硬件做了。但是并不是所有的硬件都支持这样的操作。所以 HAL 进行了抽象, SurfaceFlinger 会询问 HWC 是否支持,如果支持,就用第二种,如果不支持,就用第一种。

SurfaceFlinger 对层的融合需要 GLES, 这就需要占用显存, 还会更加耗电。

明天: 每天一点点音视频_HWC1