问题与范围
在 Swift / Metal 图像处理链路中,nearest、linear 等采样策略很容易被当作局部参数:预览缩放时希望平滑插值,像素检查或离散纹理又希望保留原始采样。然而,一个已经封装成完整 render filter 的对象,sampler 只是它的执行合同之一。
如果包裹层只替换 sampler,却遗漏 blend、raster sample count、输出合同、资源身份、uniform 与 vertex setup 或 resize 行为,问题往往不会立刻崩溃,而是变成某条输出路径的像素不同、缓存误命中,或只在透明合成时出现的异常。本文讨论如何为动态 sampler 切换建立正确的渲染合同;不讨论相机、视频编解码、设备级性能或全硬件格式覆盖。
先给结论
- sampler override 应只拥有"改变采样策略"的权力,其余 render contract 继续来自原 filter。
- sampler 变化必须进入稳定 identity 或缓存键;否则旧执行配置可能被错误复用。
- 不是每个 filter 都能安全改写 sampler:应显式区分
covered、partial、metadataOnly与notApplicable。 - 测试既要断言协议合同转发,也要读取真实像素,且覆盖 texture、frame 与 attachment 等输出路径。
为什么只换一个 sampler 不够
一个 render filter 除了采样方式,通常还声明合成模式、光栅采样数、目标纹理要求、输出色彩与 alpha 语义、资源身份,以及 uniform、顶点和尺寸计算方式。它们共同决定一次渲染如何被编码、缓存和交付。
因此,正确的 override 不是复制一份 filter 再逐项改写,而是把 base 当作其余语义的唯一事实来源。它只引入新的 sampler descriptor,并在原 identity 上追加稳定的 sampler fingerprint;blend、输出合同、资源身份、setup 与 resize 都继续转发给 base。这样新增合同字段时,适配层不会悄悄拥有第二份易漂移的实现。
可以把责任边界理解为:
text
base filter = 定义全部既有渲染语义
sampler override = 仅改变本次采样策略与对应 identity
这也解释了为什么 sampler identity 不是普通对象引用。nearest 与 linear 是不同的执行配置,缓存必须能看见这种差异;但 sampler 切换又不应让 blend、输出格式或资源身份被重定义。
覆盖能力不能用一个布尔值代替
"支持 sampler"并不等于每一个 filter 都可以接受任意 descriptor。对 runtime-bound render filter,运行时 descriptor 可以真正参与执行;但 shader-defined filter 的采样语义可能已经固定在 shader 或 pipeline 内。还有一些路径只能记录元数据,并不能保证采样行为已经改变。
Harbeth 的本轮本机源码以 SamplerExecutionCoverage 表达四种结果:
covered:当前执行路径能由 override 覆盖;partial:只覆盖其中一部分;metadataOnly:只具有描述信息,不代表实际执行已改变;notApplicable:当前 filter 不适用此能力。
这比"支持/不支持"的布尔值更诚实。调用方可以据此决定是否提供切换选项、是否需要提示能力受限,而不是把所有 filter 都承诺为可自由切换。
像素验证要走完真实输出路径
合同属性相等还不足以证明渲染正确。sampler 适配最容易遗漏的是某条输出路径没有用到同一套执行合同,所以还要读取最终像素。
本轮在 macOS arm64e 运行:
text
swift test --filter SamplerContractForwardingTests
结果为 2 项通过、0 失败,耗时 0.287 秒。第一项确认适配器转发完整 render contract,只有 sampler identity 变化。第二项在 default 与 nearest 下,分别验证 makeTexture、makeFrame、makeAttachmentSet 三条路径的预乘 alpha SourceOver 合成像素约为 [128, 0, 127, 255],容差为 1。
这两类断言分别守住"适配层没有漏转发"和"不同交付 API 没有分叉"。它们只是当前单元与像素路径的证据,不等于已完成真机视觉验收、性能验证或全 GPU/格式矩阵覆盖。
一份实用检查表
实现动态 sampler 切换前,可依次检查:
- 是否只改变 descriptor,而没有复制无关渲染逻辑?
- identity 或缓存键是否含有稳定的 sampler fingerprint?
- blend、raster sample count、输出合同、resource identity 是否仍来自 base?
- uniform、vertex setup 与
resize是否完全保持原行为? - 当前 filter 是否真的属于可运行时覆盖的路径?
- texture、frame 与 attachment 等路径是否均做过最终像素验证?
若其中任何一项不明确,就不应把"支持动态 sampler"写成无条件能力。采样策略只是一个输入配置,真正要守住的是切换前后完整渲染合同的一致性。
Harbeth 与 Kakapos 的职责边界
Harbeth 在这里负责 Apple 平台的 GPU 图像与逐帧处理,sampler override、渲染合同与像素交付均属于这一层。Kakapos 负责媒体生命周期与编排;即使由应用把 Harbeth 接入其逐帧处理链,Kakapos 也不拥有 Harbeth 内部的 sampler 执行细节。
小结
动态 sampler 切换的难点不在于创建一个新 sampler,而在于证明其余渲染语义没有被改变。把 base 作为合同事实源、把 sampler fingerprint 纳入 identity、显式表达覆盖范围,并用真实像素覆盖多条输出路径,才能让这个看似局部的配置变化保持可验证。
源码与参考实现
本文结论来自本轮本机 Harbeth 源码与测试验证,不代表已经公开发版或完成全设备验证。