动态切换 nearest / linear 时,为什么不能只替换 sampler

问题与范围

在 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 切换前,可依次检查:

  1. 是否只改变 descriptor,而没有复制无关渲染逻辑?
  2. identity 或缓存键是否含有稳定的 sampler fingerprint?
  3. blend、raster sample count、输出合同、resource identity 是否仍来自 base?
  4. uniform、vertex setup 与 resize 是否完全保持原行为?
  5. 当前 filter 是否真的属于可运行时覆盖的路径?
  6. texture、frame 与 attachment 等路径是否均做过最终像素验证?

若其中任何一项不明确,就不应把"支持动态 sampler"写成无条件能力。采样策略只是一个输入配置,真正要守住的是切换前后完整渲染合同的一致性。

Harbeth 与 Kakapos 的职责边界

Harbeth 在这里负责 Apple 平台的 GPU 图像与逐帧处理,sampler override、渲染合同与像素交付均属于这一层。Kakapos 负责媒体生命周期与编排;即使由应用把 Harbeth 接入其逐帧处理链,Kakapos 也不拥有 Harbeth 内部的 sampler 执行细节。

小结

动态 sampler 切换的难点不在于创建一个新 sampler,而在于证明其余渲染语义没有被改变。把 base 作为合同事实源、把 sampler fingerprint 纳入 identity、显式表达覆盖范围,并用真实像素覆盖多条输出路径,才能让这个看似局部的配置变化保持可验证。

源码与参考实现

本文结论来自本轮本机 Harbeth 源码与测试验证,不代表已经公开发版或完成全设备验证。

相关推荐
应用市场4 小时前
把旧 Pixel 变成相册备份中转站(上):Mac 到安卓的照片传输工具设计——流式上传、sha256 校验、adb forward 与 Bonjour
android·macos·adb·kotlin·swift
东坡肘子6 小时前
Swift Server,又多了一个 Google -- 肘子的 Swift 周报 #156
人工智能·swiftui·swift
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第7课 平台适配与互操作
android·windows·学习·ui·ios·kotlin·composer
打工仔折腾 AI1 天前
从零写一个CAD 05:以鼠标为中心的滚轮缩放,招法能复用但顺序不能反
开发语言·后端·python·性能优化·计算机外设·swift·ai agent 实战
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第5课 网络与数据层
android·网络·学习·ui·ios·kotlin·composer
茶底世界之下1 天前
视频预览切换为何会闪回旧帧:用 generation + mode identity 管住异步回调
ios·swift
老李IT笔记1 天前
从备份恢复会把管理状态带回来吗:iOS 27 之后答案变了|MDM.Plus
ios·智能手机
谢亮_vipxieliang1 天前
Go 接口设计原则核心知识点
开发语言·ios·golang
Hello_Pyhx1 天前
iPhoneMirror:把 iPhone 接入 Windows,先分清投屏与反控
windows·ios·iphone