ADR-002: OpenGL 离屏渲染统一美颜处理管线
状态: 已接受 (Accepted)
日期: 2026-04-17
最后同步: 2026-06-04(与 PhotoProcessorImpl.kt / BeautyRenderer.kt 实际代码对齐,修正 BeautyShaderChain 等已废弃设计)
决策: PM/RD 联合评审
依赖: ADR-001 (分层架构重构)
1. 背景
当前问题 (ADR-001 修复后仍存在的差距)
| 美颜效果 | 预览 (GPU Shader) | 拍照 (CPU Canvas) | 一致性 |
|---|---|---|---|
| 磨皮 | 双边滤波 (边缘保持) | 高斯模糊近似 | ⚠️ 70% |
| 美白 | YUV 亮度 Shader | ColorMatrix | ⚠️ 80% |
| 瘦脸 | FaceWarp Shader | Canvas Mesh | ⚠️ 85% |
| 大眼 | 径向放大 Shader | Canvas Mesh | ⚠️ 85% |
| 唇色 | HSV Shader | 像素级着色 | ⚠️ 75% |
| 腮红 | 椭圆染色 Shader | ColorMatrix | ⚠️ 70% |
根本原因: 两套独立实现(OpenGL ES vs Canvas API),算法细节难以完全对齐
2. 决策: OpenGL 离屏渲染统一管线
2.1 目标架构
┌─────────────────────────────────────────────────────────────┐
│ App Layer (PicMe) │
│ ↓ 依赖 beauty-engine:api │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ Domain Layer: beauty-engine:api │
│ ├─ BeautyPreviewProvider (Interface) │
│ ├─ PhotoProcessor (Interface) │
│ ├─ BeautyParams / FaceData │
│ └─ BeautyPerfStats │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ Data Layer: beauty-engine:render │
│ ├─ GlBeautyPreviewProvider │
│ ├─ PhotoProcessorImpl (OpenGL FBO 离屏渲染,2026-05 落地) │
│ │ ├─ 预览: SurfaceTexture → SurfaceView (实时) │
│ │ └─ 拍照: Bitmap → Texture → FBO → glReadPixels → Bitmap (离屏) │
│ ├─ CameraPreviewRenderer (渲染管线核心) │
│ ├─ BeautyRenderer (自研 Shader 管线,含多 Pass 渲染) │
│ │ ├─ CopyPass: OES → 2D 纹理(预览路径) │
│ │ ├─ BeautyUnitPass: 磨皮/美白/LUT │
│ │ ├─ FaceMakeupPass: 唇色/腮红三角网格 │
│ │ └─ MainShader: 美型+调色+风格特效 │
│ ├─ FaceMakeupPass (妆容三角网格 Pass) │
│ ├─ StyleEffectShader (风格特效 Shader) │
│ └─ EGLCore (EGL 上下文管理) │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────────┐
▼
┌───────────────┐
│ BIG_BEAUTY │
│ (自研 Shader) │
│ 磨皮/美白/瘦脸 │
│ 大眼/唇色/腮红 │
│ 风格特效/调色 │
└───────────────┘
2.2 核心设计
统一 Shader 管线路径:
预览路径: Camera → SurfaceTexture → BeautyRenderer 多 Pass → SurfaceView (实时 30fps)
拍照路径: Bitmap → Texture → BeautyRenderer 多 Pass → FBO → glReadPixels → Bitmap (单次)
关键洞察: - 复用同一套 BeautyRenderer 多 Pass 渲染管线(CopyPass / BeautyUnitPass / FaceMakeupPass / MainShader) - 预览和拍照只有输入/输出不同,处理逻辑完全一致 - 拍照路径跳过 CopyPass(skipCopyPass = true),因为输入已是 2D 纹理 - 彻底解决预览/拍照一致性 - PBO 异步读取当前未启用(usePbo = false),使用同步 glReadPixels
3. 技术实现
3.1 核心实现: PhotoProcessorImpl
// beauty-engine/src/main/java/com/picme/beauty/render/PhotoProcessorImpl.kt(实际落地类)
class PhotoProcessorImpl(private val context: Context) : PhotoProcessor {
private val eglCore = EGLCore()
private var eglContext: EGLContext = EGL14.EGL_NO_CONTEXT
private var eglSurface: EGLSurface = EGL14.EGL_NO_SURFACE
private var isEglInitialized = false
// 复用预览的 BeautyRenderer
private var beautyRenderer: BeautyRenderer? = null
// FBO 资源(离屏渲染输出)
private var fboId: Int = 0
private var fboTextureId: Int = 0
private var fboWidth: Int = 0
private var fboHeight: Int = 0
// PBO 当前未启用(需要 OpenGL ES 3.0)
private var usePbo: Boolean = false
// 输入纹理
private var inputTextureId: Int = 0
override fun process(bitmap: Bitmap, params: BeautyParams, faceData: FaceData?): Bitmap {
// 1. 初始化 EGL 环境(Pbuffer Surface,4096x4096)
ensureEglInitialized()
eglCore.makeCurrent(eglSurface, eglContext)
// 2. 检查纹理尺寸限制
val maxTextureSize = getMaxTextureSize()
if (bitmap.width > maxTextureSize || bitmap.height > maxTextureSize) {
throw PhotoProcessException("Bitmap exceeds max texture size")
}
// 3. 创建/复用 FBO
ensureFbo(bitmap.width, bitmap.height)
// 4. 上传 Bitmap 到 OpenGL 2D Texture
uploadBitmapToTexture(bitmap)
// 5. 初始化 BeautyRenderer(如果未初始化)
val renderer = ensureBeautyRenderer()
// 6. 设置美颜参数和人脸数据
applyBeautyParams(renderer, params, faceData)
// 7. 执行渲染(多 Pass)
val outputTexture = renderPhoto(renderer, params, bitmap.width, bitmap.height)
// 8. 读取 FBO 到 Bitmap(同步 glReadPixels)
val result = readPixelsToBitmap(bitmap.width, bitmap.height, outputTexture)
return result
}
}注意:早期设计中的
OffscreenRenderer/BeautyShaderChain类未实际落地。实际实现直接复用BeautyRenderer的多 Pass 渲染管线(renderBeautyMultiPass()/renderMainShaderFromFbo2D()),通过skipCopyPass = true跳过预览路径的 OES→2D 转换。
3.2 渲染管线复用(实际代码路径)
// 预览时:CameraPreviewRenderer 驱动 BeautyRenderer
class CameraPreviewRenderer {
private lateinit var beautyRenderer: BeautyRenderer
fun onDrawFrame() {
// 设置外部 OES 纹理和变换矩阵
beautyRenderer.setExternalTextureId(textureId)
beautyRenderer.setTextureTransform(transformMatrixBuffer)
beautyRenderer.setIsFrontCamera(isFrontCamera)
// 执行完整多 Pass 渲染
beautyRenderer.onRender()
}
}
// 拍照时:PhotoProcessorImpl 复用同一 BeautyRenderer
class PhotoProcessorImpl {
private var beautyRenderer: BeautyRenderer? = null
fun process(bitmap: Bitmap, params: BeautyParams, faceData: FaceData?): Bitmap {
val renderer = ensureBeautyRenderer()
// 设置 2D 纹理输入
renderer.setExternalTextureId(inputTextureId)
// 应用美颜参数和人脸数据
applyBeautyParams(renderer, params, faceData)
// 绑定 FBO 并执行渲染
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId)
// 判断是否需要完整多 Pass 管线
val needMultiPass = params.smoothing > 0.001f ||
params.whitening > 0.001f ||
params.bigEyes > 0.001f ||
kotlin.math.abs(params.slimFace) > 0.001f ||
params.lipColor > 0.001f ||
params.blush > 0.001f ||
params.styleEffect != StyleEffect.NONE
if (needMultiPass) {
// 使用与预览完全一致的完整多 Pass 管线
renderer.renderBeautyMultiPass(
width = width,
height = height,
outputFramebufferId = fboId,
skipCopyPass = true // 拍照路径跳过 CopyPass
)
} else {
// 无需多 Pass:直接主 Shader
renderer.renderMainShaderFromFbo2D(inputTextureId, width, height)
}
// 解绑 FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0)
// glReadPixels 读取结果
return readPixelsDirect(width, height)
}
}关键差异:早期设计中的
BeautyShaderChain类(含SmoothingShader/WhiteningShader等独立 Shader 类)未实际落地。实际实现中,GLSL 源码已迁移至assets/shaders/并通过ShaderModuleLoader按需加载;BeautyRenderer内部通过多 Pass 管线(CopyPass → BeautyUnitPass → FaceMakeupPass → MainShader/style effect)和独立的FaceMakeupPass处理妆容,通过uniform参数控制各效果开关和强度。注意:早期常量名FRAGMENT_SHADER_BEAUTY已随 GLSL 迁移而移除。
3.3 预览与拍照复用
// 预览时:由 CameraPreviewRenderer 驱动
class CameraPreviewRenderer {
private lateinit var beautyRenderer: BeautyRenderer
fun onDrawFrame() {
// 实时渲染到 SurfaceView
beautyRenderer.onRender()
}
}
// 拍照时:由 PhotoProcessorImpl 驱动
class PhotoProcessorImpl {
private var beautyRenderer: BeautyRenderer? = null
fun process(bitmap: Bitmap, params: BeautyParams, faceData: FaceData?): Bitmap {
// 离屏渲染,复用同一 BeautyRenderer 实例
val inputTexture = bitmapToTexture(bitmap)
// 设置输入并渲染到 FBO
beautyRenderer?.setExternalTextureId(inputTexture)
renderPhoto(beautyRenderer!!, params, bitmap.width, bitmap.height)
return fboToBitmap()
}
}4. 迁移计划
Phase 1: 基础设施 (2-3 周)
| 任务 | 负责人 | 输出 |
|---|---|---|
| PhotoProcessorImpl 框架 | RD | 类实现 + 单元测试 |
| FBO/Texture 管理封装 | RD | 资源管理器 |
| glReadPixels 优化 | RD | PBO 异步读取(⏳ 待优化) |
| Shader 加载器重构 | RD | 支持 .glsl 文件(⏳ 待优化) |
验收标准: - [x] PhotoProcessorImpl 可独立单元测试 - [ ] 1080p 图片处理 < 500ms - [ ] 内存无泄漏(连续处理 100 张)
Phase 2: Shader 迁移 (3-4 周)
| Shader | 当前状态 | 迁移方式 |
|---|---|---|
| 双边滤波(磨皮) | BeautyRenderer 主 Shader 内联 | 已通过 uniform 控制 |
| YUV 美白 | BeautyRenderer 主 Shader 内联 | 已通过 uniform 控制 |
| FaceWarp(瘦脸) | BeautyRenderer 主 Shader 内联 | 已通过 uniform 控制 |
| 径向放大(大眼) | BeautyRenderer 主 Shader 内联 | 已通过 uniform 控制 |
| HSV 唇色 | FaceMakeupPass 独立 Pass | 独立 Pass 处理 |
| 椭圆腮红 | FaceMakeupPass 独立 Pass | 独立 Pass 处理 |
| ColorMatrix 滤镜 | BeautyRenderer 主 Shader 内联 | 已通过 uniform 控制 |
| 风格特效 | StyleEffectShader 独立 Pass | 独立 Pass 处理 |
验收标准: - [x] 所有效果通过 BeautyRenderer 统一管线渲染 - [x] 预览效果与之前完全一致 - [ ] 拍照效果与预览 100% 一致(像素级对比)— 持续优化中
Phase 3: 拍照路径切换 (2 周)
| 步骤 | 操作 |
|---|---|
| 1 | PhotoProcessorImpl 复用 BeautyRenderer 多 Pass 管线 |
| 2 | A/B 测试:对比 CPU vs GPU 路径效果 |
| 3 | 灰度发布:50% 用户使用 GPU 路径 |
| 4 | 全量切换:100% GPU 路径 |
| 5 | 废弃 CPU 路径代码 |
验收标准: - [x] 拍照效果与预览高度一致(主观对比通过) - [x] 性能指标:1080p < 300ms(实测约 200ms) - [ ] 崩溃率 < 0.1% — 持续监控中
Phase 4: 库化准备 (4-6 周)
- 抽离
beauty-core模块(纯 Kotlin 接口) - 完善
beauty-engine模块(OpenGL 实现) - 定义稳定 API 与版本策略
- 发布内部 SDK v0.1.0
5. 风险评估
| 风险 | 概率 | 影响 | 缓解措施 |
|---|---|---|---|
| 内存压力(大图片) | 中 | 高 | PBO 异步读取,分块处理 4K |
| 兼容性(低端设备) | 中 | 中 | 保留 CPU 路径作为 Fallback |
| 开发周期延长 | 低 | 中 | 分阶段交付,每个 Phase 可独立发布 |
| Shader 精度差异 | 低 | 高 | 浮点精度统一,端到端测试 |
6. 性能预期
| 指标 | 当前 (CPU) | 目标 (GPU) | 提升 |
|---|---|---|---|
| 1080p 处理时间 | 800-1200ms | 200-300ms | 4-6x |
| 4K 处理时间 | 3-5s | 600-1000ms | 3-5x |
| 内存峰值 | 150MB | 80MB | 47%↓ |
| 预览/拍照一致性 | 70% | 99%+ | 提升 29% |
7. 依赖与前置条件
8. 决策记录
| 日期 | 决策 | 决策人 | 状态 |
|---|---|---|---|
| 2026-04-17 | 采用方案 A (OpenGL 离屏渲染) | PM/RD | 已接受 |
| 2026-04-17 | 4 阶段实施计划 | RD | 已接受 |
| 2026-04-17 | Phase 1 启动 | PM | ✅ 已完成 |
9. Phase 1 进展记录
2026-04-17 基础设施框架搭建
| 任务 | 状态 | 产出文件 |
|---|---|---|
| PhotoProcessorImpl 框架 | ✅ 完成 | PhotoProcessorImpl.kt |
| BeautyRenderer 复用 | ✅ 完成 | BeautyRenderer.kt |
| 多 Pass 管线统一 | ✅ 完成 | BeautyPass.kt, FaceMakeupPass.kt |
| 单元测试骨架 | ✅ 完成 | PhotoProcessorImplTest.kt |
已完成代码文件
PhotoProcessorImpl.kt - 核心离屏渲染器(实际落地类,替代早期 OffscreenRenderer 设计) - Bitmap → Texture → FBO → Bitmap 完整流程 - 独立 EGL 上下文(Pbuffer Surface 4096x4096) - FBO 复用与资源自动管理 - 同步 glReadPixels 读取(PBO 待后续升级) - 错误处理和边界检查
BeautyRenderer.kt - 统一渲染器(预览和拍照共用) - 主 Shader(FRAGMENT_SHADER_BEAUTY)处理磨皮/美白/美型/调色 - FaceMakeupPass 独立 Pass 处理唇色/腮红 - StyleEffectShader 独立 Pass 处理风格特效 - renderBeautyMultiPass() 完整多 Pass 管线 - renderMainShaderFromFbo2D() 拍照路径快捷入口
BeautyPass.kt / FaceMakeupPass.kt - Pass 基类与妆容 Pass - BeautyPass: 通用渲染 Pass 封装 - FaceMakeupPass: 三角网格 + 纹理贴图的妆容渲染
验收进度
Phase 1 验收标准: - [x] PhotoProcessorImpl 框架实现(支持 Bitmap 输入输出) - [x] 单元测试通过(真实 EGL 环境验证) - [x] 1080p 图片处理 < 500ms(设备实测 200ms 以内) - [x] 内存无泄漏测试(连续 100 张压力测试通过)
落地实现记录(2026-05)
GPU 离屏渲染拍照已由 PhotoProcessorImpl 在 beauty-engine/render 中完整落地,关键实现要点:
- 独立 EGL 上下文与 Pbuffer Surface:在
PhotoProcessorImpl中创建独立 EGL 上下文,避免与预览线程竞争,实现后台线程异步处理。 - 渲染管线复用:直接调用
BeautyRenderer.renderBeautyMultiPass()/renderMainShaderFromFbo2D(),复用预览同一套渲染逻辑。拍照路径设置skipCopyPass = true跳过 OES→2D 转换。 - 坐标系统一:拍照路径直接使用原始人脸关键点坐标,跳过预览路径的
inverseTransform,Bitmap 上传后的 UV 空间与 Shader 期望的标准 UV 空间天然对齐。 - FBO 复用与 glReadPixels:实现
ensureFbo(width, height)按需重建;采用同步glReadPixels读取,主流设备 1080P 处理耗时控制在 200ms 以内。 - 黑屏修复:确保
BeautyRenderer在PhotoProcessorImpl线程内完成onInit,显式设置glViewport,解决跨上下文 Uniform 缓存失效问题。
注: - 原
GPU_PHOTO_MAJOR_CHANGES.md已归档合并至本文档。 - 早期设计中的BeautyShaderChain/OffscreenRenderer/ 独立 Shader 类(SmoothingShader等)未实际落地,实际实现直接复用BeautyRenderer统一管线。 - PBO 异步读取当前未启用(usePbo = false),后续作为优化项评估。
当前状态: Phase 1 已落地,PhotoProcessorImpl 生产可用。后续优化(PBO 异步读取、多分辨率压测)归入 P1 迭代。
设计演变说明:本文档中的
BeautyShaderChain、OffscreenRenderer以及独立的SmoothingShader/WhiteningShader等类属于早期设计草案,未实际落地。实际实现直接复用BeautyRenderer统一多 Pass 管线,通过uniform参数控制各效果开关。此简化降低了架构复杂度,同时保证了预览/拍照效果一致性。