一帧进 Metal:按平面取纹理,持到命令缓冲完成

渲染侧先建一个纹理缓存,它跟着设备走、只建一次。解码帧是一个像素缓冲,从它身上按平面各取一张纹理:亮度一张、色度一张,格式按位深与色度是否缩半来定。
这两张纹理是从缓存里借出来的,不是新的持有方;真正压着这块内存的是那个在解码回调里加过一次引用的像素缓冲。所以这里的规矩是:借来的纹理与加过引用的缓冲都要活到命令缓冲跑完,在完成回调里统一放掉。缓存自己的退场也由一层共享状态管:记在飞的数量与待释放的缓存,同样在完成回调里兑现。
交给 Vulkan:共的是那个面
渲染把平面画成一张 RGBA 纹理,而这张纹理是用一个面建的——那个面就是交给 Vulkan 的东西。
VK 侧的输入层这样接:从 Metal 那个上下文取面,只在尺寸或面本身变了的时候才换;建图时按「导入 Metal 面」这种外部类型声明,把这个面直接导成一张 VkImage,格式按 8bit RGBA 映射。
跨过去的不是 Metal 的纹理对象,是那张面。同一块内存在两个 API 各有一个视图,和 Windows 上的句柄、Android 上的那块缓冲是同一种做法,只是这里共享的是面。
这里没有跨 API 的显式同步对象。顺序靠约定:同一帧先由 Metal 出 RGBA,再由 VK 接手;样例里是靠连续多帧读回都正确来旁证这条链成立的,有没有撕裂没有实测过。
原生直呈:面归宿主,提交在渲染器
开关关着的时候,窗口那一侧拿到的是宿主给的视图,渲染器把视图里的那一层当自己的目标层:每帧取一个可绘制的对象、把画面画上去、提交,并结束一次事务让这一帧真的上屏。
这一侧「层」这个概念是宿主给的:视图本身归宿主,渲染器拿到的只是它上面那一层。所以尺寸、可见性这些也归宿主管——渲染器只管往这一层里画。这与另外两个平台不同:Windows 的窗口实现自己建交换链,Android 的渲染器自己建面。
开关开着的时候,窗口对象换成 Vulkan 那一类,平台渲染器改成离屏——它的那张 RGBA 不再上屏,而是走上面那条路交给 VK。两条路互斥的是呈现那一步,与另外两个平台一致。
MoltenVK 这条路上会直接崩的几处:引导、空模块、行对齐、兼容开关

这套东西能跑起来,靠的是在一个 Vulkan 实现之上铺一层到 Metal 的翻译。这几处不处理会直接崩:
引导:真机上要按框架路径加载那一层并用它的加载入口初始化函数表,找不到才退到全局符号;两处都没有的话,直接落回 Metal 原生那条腿。
空的着色器模块:翻译层碰到空句柄的模块会直接段错误,所以在建管线时,哪一层没有模块就跳过这一层。
面的行字节数对齐:这个面每行字节数要满足一个对齐要求,否则在某些宽度上会断言——代码是按更宽的对齐算的。
兼容开关:解码会话与渲染这两处都必须声明那一帧与 Metal 兼容;少了它,纹理缓存映射会「成功」但布局无效,一采样就是页错误。前面硬解接入那一篇里那条「宁可不用某个键也不带 GL 兼容键」的判断,正是为了不把会话建在另一条废弃的路上。
颜色空间:直通那一档要把层配成扩展线性色彩空间,并宣告这个层要显示扩展动态范围的内容。
呈现时机:VK 那一侧的呈现用信号量与栅栏串起来,并在节流里打点,不等同步。
输入层每帧的判据,与那条面的归属
输入层不是每帧都重建:它每次读一遍那个面的尺寸与标识,尺寸或面本身换了才重建图。这与 Android 那边「判据要连上下文一起算」是同一类问题——同一个资源被复用时,只看名字或编号会拿到旧尺寸的图。
那条面是渲染侧创建的,做导入的一方只是借用;所以释放的次序要跟着渲染侧的节奏走,提前放掉就是采样到已经回收的显存。这一侧的归属是:面归渲染,纹理归缓存,缓冲的引用归解码。
呈现这一侧:取图、等栅栏、提交、呈现
Vulkan 窗口这一侧的呈现不是一句调用就完,它把取图、等栅栏、提交、呈现几样串在一起,并给呈现结果留了打点,免得这一句慢的时候看不出来。
多线程提交这件事也有一条规矩:共用一台设备的多条线程提交命令时要保持同步,靠的是一把提交锁。所以提交之前要先拿那把提交锁。
反方向那条:VK 的结果回到 Metal
Vulkan 合成完的输出面也能导成一个系统面,再包成图像缓冲交给 Metal 读回;硬编那一侧吃的就是这条面上的缓冲。所以这一对方向是齐的:输入是「Metal 出、VK 收」,输出是「VK 出、Metal 收」。
两条路一条落在宿主的那一层上、由渲染器提交;一条把面交给 Vulkan,共用的是那张 RGBA 与它背后的面。与另外两个平台比,共享对象的形态不同,做的事是同一次转换、两条去处。
引擎 avox 在 GitHub 开源:github.com/xxxzhou/avox,纹理缓存、那张共享面与 MoltenVK 那几个守卫都在里面。
音视频开发 播放器 硬件解码 Metal MoltenVK IOSurface