分享

【Android】Graphics

 开花结果 2023-05-26 发布于北京

1.图形绘制概述

Android 框架提供了各种用于 2D 和 3D 图形渲染的 API,可与制造商的图形驱动程序实现代码交互,因此,务必更好地了解这些 API 的工作原理。本页介绍了在其上构建这些驱动程序的图形硬件抽象层 (HAL)。

HAL Graphic

Android框架提供了三种绘制图形的方式:Canvas,OpenGL ES 和Vulkan.

Android 图形组件

图 1. Surface 如何被渲染

主要组件如下所述:

图像流生产者(Image Stream Producers):图像流生产方可以是生成图形缓冲区以供消耗的任何内容。例如 OpenGL ES、Canvas 2D 和 mediaserver 视频解码器。

图像流消费者(Image Stream Consumers):图像流的最常见消耗方是 SurfaceFlinger,该系统服务会消耗当前可见的 Surface,并使用窗口管理器中提供的信息将它们合成到屏幕。SurfaceFlinger 是可以修改所显示部分内容的唯一服务。SurfaceFlinger 使用 OpenGL 和 Hardware Composer 来合成一组 Surface。其他 OpenGL ES 应用也可以消耗图像流,例如相机应用会消耗相机预览图像流。非 GL 应用也可以是使用方,例如 ImageReader 类。

硬件混合渲染器(Hardware Composer):显示子系统的硬件抽象实现。SurfaceFlinger 可以将某些合成工作委托给硬件混合渲染器,以分担 OpenGL 和 GPU 上的工作量。SurfaceFlinger 只是充当另一个 OpenGL ES 客户端。因此,在 SurfaceFlinger 将一个或两个缓冲区合成到第三个缓冲区中的过程中,它会使用 OpenGL ES。这会让合成的功耗比通过 GPU 执行所有计算时更低。硬件混合器HAL则进行另一半的工作,是所有 Android 图形渲染的中心点。其中之一是 VSYNC(另一个是支持即插即用 HDMI 的热插拔)。硬件混合器HAL:硬件混合渲染器 (HWC) HAL 用于确定通过可用硬件来合成缓冲区的最有效方法。作为 HAL,其实现是特定于设备的,而且通常由显示硬件原始设备制造商 (OEM) 完成。

当您考虑使用叠加平面时,很容易发现这种方法的好处,它会在显示硬件(而不是 GPU)中合成多个缓冲区。例如,假设有一部普通 Android 手机,其屏幕方向为纵向,状态栏在顶部,导航栏在底部,其他区域显示应用内容。每个层的内容都在单独的缓冲区中。您可以使用以下任一方法处理合成:

方法1:将应用内容渲染到暂存缓冲区中,然后在其上渲染状态栏,再在其上渲染导航栏,最后将暂存缓冲区传送到显示硬件。(见下图)

方法2:将三个缓冲区全部传送到显示硬件,并指示它从不同的缓冲区读取屏幕不同部分的数据。

后一种方法可以显著提高效率。

显示处理器功能差异很大。叠加层的数量(无论层是否可以旋转或混合)以及对定位和重叠的限制很难通过 API 表达。为了适应这些选项,HWC 会执行以下计算:

1.SurfaceFlinger 向 HWC 提供一个完整的层列表,并询问“您希望如何处理这些层”?

2.HWC的响应方式是将每个层标记为设备或者客户端合成。

3.SurfaceFlinger 会处理所有客户端,将输出缓冲区传送到 HWC,并让 HWC 处理其余部分。

由于硬件供应商可以定制决策代码,因此可以在每台设备上实现最佳性能。

当屏幕上的内容没有变化时,叠加平面的效率可能会低于 GL 合成。当叠加层内容具有透明像素且叠加层混合在一起时,尤其如此。在此类情况下,HWC 可以为部分或全部层请求 GLES 合成,并保留合成的缓冲区。如果 SurfaceFlinger 要求合成同一组缓冲区,HWC 可以显示先前合成的暂存缓冲区。这可以延长闲置设备的电池续航时间。

Android 设备通常支持 4 个叠加平面。尝试合成的层数多于叠加层数会导致系统对其中一些层使用 GLES 合成,这意味着应用使用的层数会对能耗和性能产生重大影响。

Gralloc:需要使用图形内存分配器 (Gralloc) 来分配图像生产者请求的内存。BufferQueue 和 Gralloc:

BufferQueue 类将可生成图形数据缓冲区的组件(生产方)连接到接受数据以便进行显示或进一步处理的组件(使用方)。几乎所有在系统中移动图形数据缓冲区的内容都依赖于 BufferQueue。

数据流

有关 Android 图形管道的描述,请参见下图:

图 2. 流经 Android 的图形数据流 /Figure 2. Graphic data flow through Android

左侧的对象是生成图形缓冲区的渲染器,如主屏幕、状态栏和系统界面。SurfaceFlinger 是合成器,而硬件混合渲染器是制作器。

The objects on the left are renderers producing graphics buffers, such as the home screen, status bar, and system UI. SurfaceFlinger is the compositor and Hardware Composer is the composer.

BufferQueue

BufferQueues 是 Android 图形组件之间的粘合剂。它们是一对队列,可以调解缓冲区从生产方到消耗方的固定周期。一旦生产方移交其缓冲区,SurfaceFlinger 便会负责将所有内容合成到显示部分。

图 3. BufferQueue 通信过程

BufferQueue 包含将图像流生产方与图像流消耗方结合在一起的逻辑。BufferQueue 包含将图像流生产方与图像流消耗方结合在一起的逻辑。

BufferQueue 是将缓冲区池与队列相结合的数据结构,它使用 Binder IPC 在进程之间传递缓冲区。生产方接口,或者您传递给想要生成图形缓冲区的某个人的内容,即是 IGraphicBufferProducer。BufferQueue 通常用于渲染到 Surface,并且与 GL 消耗方及其他任务一起消耗内容。

BufferQueue 可以在三种不同的模式下运行:

类同步模式(Synchronous-like mode): 默认情况下,BufferQueue 在类同步模式下运行,在该模式下,从生产方进入的每个缓冲区都在消耗方那退出。在此模式下不会舍弃任何缓冲区。如果生产方速度太快,创建缓冲区的速度比消耗缓冲区的速度更快,它将阻塞并等待可用的缓冲区。

非阻塞模式(Non-blocking mode):BufferQueue 还可以在非阻塞模式下运行,在此类情况下,它会生成错误,而不是等待缓冲区。在此模式下也不会舍弃缓冲区。这有助于避免可能不了解图形框架的复杂依赖项的应用软件出现潜在死锁现象。

舍弃模式(Discard mode):最后,BufferQueue 可以配置为丢弃旧缓冲区,而不是生成错误或进行等待。例如,如果对纹理视图执行 GL 渲染并尽快绘制,则必须丢弃缓冲区。

为了执行这项工作的大部分环节,SurfaceFlinger 就像另一个 OpenGL ES 客户端一样工作。例如,当 SurfaceFlinger 正在积极地将一个缓冲区或两个缓冲区合成到第三个缓冲区中时,它使用的是 OpenGL ES。

Hardware Composer HAL 执行另一半工作。该 HAL 充当所有 Android 图形渲染的中心点。

Canvas

Canvas(画布)实现是由Skia图形库实现。如果您要绘制一个矩形,可以调用 Canvas API,它会在缓冲区中适当地设置字节。为了确保两个客户端不会同时更新某个缓冲区,或者在该缓冲区正在被显示时写入该缓冲区,请锁定该缓冲区以进行访问。您可以使用以下命令处理画布锁:

lockCanvas():可锁定缓冲区以在 CPU 上渲染,并返回用于绘图的画布。

unlocklockCanvasAndPost():可解锁缓冲区并将其发送到合成器。

lockHardwareCanvas():可锁定缓冲区以在 GPU 上渲染,并返回用于绘图的画布。

注意:

【1】:当应用通过lockCanvas()锁定 Surface 时,所获得的画布一概不会获得硬件加速。

【2】:如果您曾调用过lockCanvas(),则无法使用 GLES 在 Surface 上绘图或从视频解码器向其发送帧。lockCanvas()会将 CPU 渲染程序连接到 BufferQueue 的生产方,直到 Surface 被销毁时才会断开连接。与大多数生产方(如 GLES 或 Vulkan)不同,基于画布的 CPU 渲染程序无法在断开连接后重新连接到 Surface。

生产方首次从 BufferQueue 请求某个缓冲区时,该缓冲区将被分配并初始化为零。必须进行初始化,以避免意外地在进程之间共享数据。但是,如果您重复使用缓冲区,以前的内容仍会存在。如果您反复调用 lockCanvas() 和 unlockCanvasAndPost() 而不绘制任何内容,则生产方会在先前渲染的帧之间循环。

Surface 锁定/解锁代码会保留对先前渲染的缓冲区的引用。如果在锁定 Surface 时指定了脏区域,那么它将从以前的缓冲区复制非脏像素。SurfaceFlinger 或 HWC 通常会处理缓冲区;但是由于我们只需从缓冲区中读取内容,因此无需等待独占访问权。

EGLSurface 和 OpenGL ES

Android 使用OpenGL ES(GLES) API渲染图形。 为了创建GLES 上下文并未GLES渲染提供窗口系统,Android使用EGL调用用于渲染到屏幕上。

在使用GLES进行绘制之前,你需要创建GL上下文。在EGL中,这意味着要创建的一个EGL Context和一个EGL Surface。GLES 操作适用于当前上下文,该上下文通过线程局部存储访问,而不是作为参数进行传递。渲染代码应该在当前GLES线程(而不是界面线程)上执行。

EGLSurface:

EGLSurface 可以是由 EGL 分配的离屏缓冲区(称为“pbuffer”),也可以是由操作系统分配的窗口。

调用eglCreateWindowSurface()函数可创建 EGL 窗口 Surface。eglCreateWindowSurface()将“窗口对象”作为参数,在 Android 上,该对象是 Surface。Surface 是 BufferQueue 的生产方端。消费方(SurfaceView、SurfaceTexture、TextureView 或 ImageReader)创建 Surface。当您调用eglCreateWindowSurface()时,EGL 将创建一个新的 EGLSurface 对象,并将其连接到窗口对象的 BufferQueue 的生产方接口。此后,渲染到该 EGLSurface 会导致一个缓冲区离开队列、进行渲染,然后排队等待消费方使用。

注意:虽然窗口通常会显示,但是在这种情况下,EGLSurface 窗口的输出可能不会出现在屏幕上。

EGL 不提供锁定/解锁调用。您需要发出绘制命令,然后调用eglSwapBuffers()来提交当前帧。方法名称来自传统的前后缓冲区交换,但实际实现可能有所不同。

一个 Surface 一次只能与一个 EGLSurface 关联(您只能将一个生产方连接到一个 BufferQueue),但是如果您销毁该 EGLSurface,它将与该 BufferQueue 断开连接,并允许其他内容连接到该 BufferQueue。

通过更改“当前”EGLSurface,指定线程可在多个 EGLSurface 之间进行切换。一个 EGLSurface 一次只能在一个线程上处于当前状态。

EGL 不是 Surface 的另一方面(如 SurfaceHolder)。EGLSurface 是一个相关但独立的概念。您可以在没有 Surface 作为支持的 EGLSurface 上绘制,也可以在没有 EGL 的情况下使用 Surface。EGLSurface 只是为 GLES 提供一个绘制的地方。

如需了解 OpenGL ES 和 EGL 要求,请参阅 Android兼容性定义文档。

ANativeWindow

公开的 Surface 类以 Java 编程语言实现。C/C++ 中的同等项是 ANATIONWindow 类,由Android NDK半公开,可以使用ANativeWindow_fromSurface()调用从 Surface 获取 ANativeWindow。就像它的 Java 语言同等项一样,您可以对 ANativeWindow 进行锁定、以软件形式进行渲染,以及解锁并发布。基本的“原生窗口”类型是 BufferQueue 的生产方端。

如需从原生代码创建 EGL 窗口 Surface,可将 EGLNativeWindowType 的实例传递到eglCreateWindowSurface()。EGLNativeWindowType 是 ANativeWindow 的同义词,因此可以互换使用。

Vulkan

Android 支持Vulkan,这是一套适用于高性能 3D 图形的低开销、跨平台 API。与 OpenGL ES(GLES)一样,Vulkan也提供在应用中创建高品质实时图形的工具。

使用Vulkan的优势包括降低CPU开销以及支持SPIR-V二进制中间语言。

SOC厂商(例如GPU独立供应商(IHV))可以编写适用于Android的Vulkan驱动程序。原始设备制造商(OEM)只需要为特定设备集成这些驱动程序即可。如需详细了解 Vulkan 驱动程序如何与系统进行交互、应如何安装特定于 GPU 的工具以及特定于 Android 的要求,请参阅实现 Vulkan。

应用开发者可以使用 Vulkan 来打造在 GPU 上执行命令的应用,大幅降低开销。与EGL和GLES相比,Vulkan 还可以更直接地映射到当前图形硬件中的功能,Vulkan 还可以更直接地映射到当前图形硬件中的功能。

如需了解 Vulkan 的常规信息,请参阅Vulkan 概览或查看资源列表。

Vulkan组件

对 Vulkan 的支持包含以下组件。

Vulkan 组件组件名称提供者说明

Vulkan验证层

Android(在NDK中)

在Vulkan应用开发期间使用的库,用于查找应用在Vulkan API 使用方面的错误。在找到此类错误后,应移除这些库。

Vulkan运行时

Android

原生库 libvulkan.so 提供原生 Vulkan API。

Vulkan 运行时的大部分功能由 GPU 供应商提供的驱动程序实现。Vulkan 运行时会封装驱动程序、提供 API 拦截功能(针对调试和其他开发者工具)以及管理驱动程序与平台依赖项之间的交互。

Vulkan 驱动程序

SoC

将 Vulkan API 映射到特定于硬件的 GPU 命令以及与内核图形驱动程序的交互。

有修改的组件

BufferQueue 和 Gralloc 支持 Vulkan:

BufferQueue。BufferQueue 中的额外枚举值和方法以及ANativeWindow 接口可让 Vulkan 运行时通过ANativeWindow连接到 BufferQueue。

Gralloc。可选接口。通过它,Gralloc 可以了解是否能够将某种指定格式用于特定生产方/消费方组合,而无需分配缓冲区。

资源

通过以下资源详细了解 Vulkan:

Vulkan 加载程序 (libvulkan.so),位于 platform/frameworks/native/vulkan。其中包含 Android 的 Vulkan 加载程序,以及一些对平台开发者十分有用的 Vulkan 相关工具。

实现 Vulkan。旨在帮助 GPU IHV 编写适用于 Android 的 Vulkan 驱动程序,以及指导原始设备制造商 (OEM) 为特定设备集成这些驱动程序。该指南介绍了 Vulkan 驱动程序如何与系统进行交互、应该如何安装特定于 GPU 的工具以及特定于 Android 的实现要求。

Vulkan Graphics API 指南。介绍了如何开始在 Android 应用中使用 Vulkan、Android 平台上的 Vulkan 设计指南、如何使用 Vulkan 的着色程序编译器以及如何使用验证层来帮助确保使用 Vulkan 的应用的稳定性。

Vulkan 新闻:包含事件、补丁程序、教程以及更多与 Vulkan 相关的新闻报道。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多