背景与现象
这是 OBS Studio 鸿蒙迁移系列的第三篇。装机首启时,UI 已经完整渲染出来——场景面板、来源按钮、推流配置都在,但顶栏只剩一行,日志里是- obs_reset_video failed: -1
复制代码 。libobs 内核活着,图形后端没起来。
真正麻烦的是:错误信息一个字都没有。libobs 的日志函数 blog() 默认写 stderr,而鸿蒙应用没有 stderr 控制台,所有错误直接蒸发。所以图形链排障的第一步不是查图形,而是先给库接上宿主日志系统。
第一步:先接通 hilog,再谈图形
在 obs_startup 之前挂上 base_set_log_handler,把 libobs 的日志转发到 hilog:
- // obs_bridge.cpp —— 在 obs_startup 之前
- base_set_log_handler(ObsLogToHilog, nullptr);
- static void ObsLogToHilog(void *, int log_level, const char *format, va_list args) {
- // libobs 的 LOG_WARNING/LOG_ERROR 与 hilog 的 LogLevel 枚举撞名,
- // 同一编译单元里直接编译错误——用数值阈值绕开
- OH_LOG_Print(LOG_APP, log_level >= 200 ? LOG_WARN : LOG_ERROR, ...);
- }
复制代码
接上日志的瞬间,真实错误全部现形:default.effect 编译失败,并且一次失败里连环套着四个问题。这条经验适用于任何 C/C++ 库的移植——先接日志,再定位问题,否则后面所有坑都无从谈起。
四连击:桌面 GLSL 方言在 ES 里的水土不服
OBS 的着色器不是手写 GLSL,而是一种 HLSL 风格的 .effect 语言,由 gl-shaderparser.c 在运行时翻译成 GLSL。桌面版翻译目标是 #version 330,鸿蒙只有 GLES 3.2,目标必须换成 #version 300 es。关键在于:ES 与桌面 GLSL 不是子集关系,而是另一门方言,类型系统严格得多。
第一击:out gl_PerVertex 重声明被拒
生成器会给每个顶点着色器固定输出一段:
- out gl_PerVertex {
- vec4 gl_Position;
- };
复制代码
桌面 GLSL 330 里这是标准写法。但 GLSL ES 3.0 中 gl_PerVertex 是隐式声明的,再声明一遍带显式存储限定符的接口块会直接报错:Invalid qualifier 'out' for interface block。修法是在 __OHOS__ 分支跳过 gl_write_interface_block(),gl_Position 不声明也能用。
第二击:textureSize 返回 ivec,不隐式转 vec
生成器 preamble 的辅助函数里有一句- vec2 size = textureSize(s, lod);
复制代码 。桌面 GLSL 允许 ivec→vec 的隐式向量转换,ES 不允许,textureSize 返回 ivec2,赋给 vec2 直接报错。修法是显式包一层:vec2(textureSize(s, lod))。
第三击:textureLod 第三参要求 float,传了 int
- int lod = p_lod.z;
- vec4 color = textureLod(s, p, lod); // ES: 找不到匹配重载
复制代码
桌面 GLSL 出于向后兼容允许这里隐式 int→float,ES 拒绝。修法是 textureLod(s, p, float(lod))。
第四击:4095 少了小数点,实为上游笔误
format_conversion.effect(YUV/RGB 色彩转换着色器)里 12bit 视频路径写着:
同文件基础版写的是 4095.(带小数点),PQ/HLG 两个 HDR 变体漏了。float 除以 int 字面量,桌面宽容、ES 拒绝。这不是鸿蒙不支持,而是 OBS 上游代码里的真实笔误,只是桌面 shader 模型太宽容,一直没炸。
顺手还揪出一个同族问题:5 处- image.Sample(def_sampler, uv, 0)
复制代码 。HLSL 的 Sample 第三参是纹素偏移,恒为 0 还显式传参,libobs 的 GL 翻译器生成的代码在 GLES 下找不到合法重载,删参即可。
这两个 effect 文件的修复被拆成独立 PR 提给上游(pr/effect-format-fixes 分支)。理由是它是纯 bugfix,与鸿蒙无关,对未来任何要过严格 HLSL→GLSL 翻译的 GLES 目标(比如 Android 移植者)都有价值。
sampler2DRect:GLES 的保留字陷阱
四连击修完,default.effect 过了,但 OBS 启动时还要求加载 default_rect.effect——矩形纹理专用着色器。矩形纹理是桌面 GL 的概念,GLES 根本没有这个功能,sampler2DRect 在 ES 里是保留字,编译必炸。修法在 libobs/obs.c 里跳过加载:
- #ifndef __OHOS__
- load_effect(&data->rect_effect, "default_rect.effect", ...);
- #endif
复制代码
注意一个细节:加载和它对应的“必需 effect 缺失”校验两处都要跳过。只跳加载会卡在启动校验上——libobs 把 default_rect 列为必需资源,这个“必需”在 GLES 平台上的语义需要整体改写,而不是打一处补丁。
Maleoon 三连拒:驱动“支持”与驱动“实现”是两件事
shader 全过之后,撞上最黑的一堵墙:创建 GS_BGRA 格式的渲染纹理时,glTexImage2D 报 GL_INVALID_OPERATION。按规范查,ES 3.0 纹理格式合法组合表里 (internalformat=SRGB8_ALPHA8, format=BGRA_EXT, type=UNSIGNED_BYTE) 是白纸黑字的合法组合;glGetString(GL_EXTENSIONS) 也明确 advertised 了 GL_EXT_texture_format_BGRA8888。规范说合法、驱动说支持,但调用就是失败。
加参数 dump 探针抓到失败三元组后开始试错,最终落到方案 3:GS_BGRA 在 OHOS 分支退让成 GL_RGBA8 + GL_RGBA + GL_UNSIGNED_BYTE。
这一课的核心结论:扩展被 advertised ≠ 该扩展的所有合法组合被实现。驱动合规性上“规范是规范,实现是实现”,唯一可信的验证方式是拿真硬件实测,这也是图形后端必须上真机才算数的原因。
代价也要如实记录:BGRA 退成 RGBA 后,内存字节序与 libobs 假设不再匹配,色彩通道存在隐患。后来录制链路“整幅偏红”的排查,以及至今未结的色准精调项,都与这次退让有关。移植没有免费午餐,每个 workaround 都是记账。
三关模型:着色器要在 GLES 上活下来
复盘这一战役,OBS 的 effect 着色器想在 GLES 设备上跑通,需要过三关:
第一关,翻译器关(gl-shaderparser.c):生成的 GLSL 文本本身要符合 ES 语法与类型规则;
第二关,源文件关(*.effect):上游着色器源码里的宽容依赖(int 字面量、冗余参数、桌面专属概念)要清干净;
第三关,驱动关(Maleoon 916B):规范允许 ≠ 驱动实现,纹理格式组合必须实测。
三关都要过一遍,任何一关的“看起来没问题”都不算数。其中 shader parser 的 GLSL ES 兼容三连(gl_PerVertex 跳过、ivec 显式转换、textureLod 传 float)同样拆成独立 PR(pr/glsl-es-compat 分支),对任何 GLES-only 目标都有独立价值。判断“什么值得提上游”的标尺就是目标无关性。
验证环境
验证机为 HUAWEI MatePad Edge(PC/2in1 模式),HarmonyOS 7.0.0.109,API 26,Maleoon 916B(GLES 3.2);OBS Studio 32.2.2,fork 分支 qingkouwei/obs-studio@harmonyos-port。下一篇讲图形后端战役下半场:一个枚举值撞车引发的全黑预览,以及从红帧实验到 GPU 内省 dump 的排障过程。 |