解码 Android 图形渲染管线:从窗口创建到 HWC 合成上屏

0 阅读3分钟

在深入研究 Android 图形系统时,很容易迷失在碎片化的概念中。今天,让我们揭开从 App 请求新建窗口,到最终屏幕亮起像素的完整物理流转过程。

1. 控制面:窗口诞生与画布分配

当 App 需要新建窗口时,它会将参数(尺寸、透明度、标志位等)发送给 WMS(窗口管理服务)。校验通过后,WMS 通过 Binder 通知 SurfaceFlinger 在内存中创建一个 Layer(图层),该 Layer 承载了空间属性(Z 轴层级、显示尺寸、透明度等)。
同时,SurfaceFlinger 创建了一个生产者-消费者队列(BufferQueue),并为该 Layer 生成一个控制句柄:SurfaceControl。该句柄一路回传给 App 的 ViewRootImpl,ViewRootImpl 拿着它创建 Surface,并向 SurfaceFlinger 请求获取 BufferQueue 的生产端句柄。

2. 数据面:异步渲染与 Fence 同步

当 RenderThread 接收到主线程传来的 DisplayList 时,会将其翻译成可执行的 OpenGL 指令。
接着,它通过 Surface 检查是否有可用的 GraphicBuffer。如果没有,则通过 Gralloc 向内核申请对 GPU 友好的物理内存,并将该 Buffer 的状态标记为 DEQUEUED(已出队)。
关键点在于:将指令提交给 GPU 后,CPU 并不做同步等待。它立刻将该 Buffer 入队,并标记为 QUEUED(已入队)。由于此时 GPU 仍在后台渲染,该 Buffer 附带的 Fence 信号处于锁定(未解锁)状态;只有当 GPU 渲染完成,Fence 才会解锁。

3. 合成面:VSYNC 驱动的生命周期

当硬件 VSYNC 信号到来时,SurfaceFlinger 会遍历所有的 BufferQueue。
它检查队头的 Buffer:如果 Fence 已解锁(代表渲染完成),则取走该 Buffer,并将其状态标记为 ACQUIRED(已获取)。
随后,SurfaceFlinger 遍历所有 Layer,填入新的 Buffer(没有新帧的 Layer 则复用上一帧的 Buffer),并将它们交给硬件合成器(HWC) 进行最终的合成与显示。合成完成后,旧的 Buffer 状态重置为 FREE(空闲),重新归还给队列以供后续复用。

💡 架构师总结:

这种 “控制面/数据面分离” ,结合 “多缓冲状态机” 与 “异步 Fence 同步” 的架构,实现了跨进程的零拷贝传输。它彻底消除了主线程阻塞,并根除了画面撕裂。这是 Android 实现丝滑 120Hz 渲染的终极基石。



🇬🇧 English Version

Decoding the Android Graphics Pipeline: From Window Creation to HWC Composition

When diving deep into Android’s graphics system, it’s easy to get lost in fragmented concepts. Today, let’s unveil the complete physical flow from when an App requests a new window to the exact moment pixels light up on the screen.

1. Control Plane: Window Genesis & Canvas Allocation
When an App needs a new window, it sends parameters (size, alpha, flags, etc.) to WMS. After validation, WMS instructs SurfaceFlinger via Binder to create a Layer in memory, which holds spatial attributes (Z-order, display size, alpha).
Simultaneously, SurfaceFlinger creates a Producer-Consumer queue (BufferQueue) and generates a control handle for the layer: SurfaceControl. This handle is passed back to the App’s ViewRootImpl, which instantiates a Surface and requests the Producer handle of the BufferQueue from SurfaceFlinger.

2. Data Plane: Asynchronous Rendering & Fence Synchronization
When RenderThread receives the DisplayList from the main thread, it translates it into executable OpenGL commands.
It then checks the Surface for an available GraphicBuffer. If none exists, it requests GPU-friendly physical memory from the kernel via Gralloc, marking the buffer’s state as DEQUEUED.
Crucially, after submitting commands to the GPU, the CPU does not wait synchronously. It immediately enqueues the buffer, marking it as QUEUED. Since the GPU is still rendering in the background, the buffer’s attached Fence signal remains locked (off) ; it only unlocks once the GPU finishes.

3. Composition Plane: VSYNC-Driven Cycle
When the hardware VSYNC signal arrives, SurfaceFlinger iterates through all BufferQueues.
It inspects the head of the queue: if the Fence is unlocked (rendering complete), it acquires the buffer, marking it as ACQUIRED.
SurfaceFlinger then iterates through all Layers, filling in the new buffers (layers without new frames reuse the previous frame’s buffer), and passes them to the Hardware Composer (HWC)  for final composition and display. Once composition is done, the old buffer’s state resets to FREE, returning to the queue for reuse.

💡 Architect’s Takeaway:
This architecture — Control/Data Plane separation paired with a Multi-buffering State Machine & asynchronous Fence sync — achieves zero-copy cross-process transmission. It eliminates main-thread blocking and completely eradicates screen tearing. This is the ultimate bedrock enabling Android’s buttery-smooth 120Hz rendering.