You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Metal着色器叠加Overlay时水平拉伸2倍的问题排查

问题原因分析

你的Overlay被水平拉伸2倍,且GPU捕获显示Y/UV分量存在左右重复的图像,核心问题出在Metal纹理与原CVPixelBuffer的格式/布局不匹配,具体可能是以下几点:

  • 纹理像素格式不匹配:原CVPixelBuffer是kCVPixelFormatType_422YpCbCr10BiPlanarVideoRange(10位4:2:2格式),但你将Y纹理创建为r16Unorm、UV纹理创建为rg16Unorm(16位格式),导致Metal错误解析内存中的10位数据,将原本的单帧图像解析为重复的两部分。
  • 纹理宽度参数错误:创建Y/UV纹理时,错误设置了宽度值(比如将Y纹理宽度设为960而非1920),同时纹理的wrapS模式为MTLWrapModeRepeat,导致线程调度时超过纹理宽度的坐标被重复映射,最终写入重复的图像内容。
  • CVPixelBuffer字节行对齐问题:原像素缓冲区的Y/UV平面字节行间距与Metal纹理的行字节数不匹配,导致内存读取时出现偏移,进而产生图像重复。

解决方案

1. 严格匹配纹理与像素缓冲区的格式

从CVPixelBuffer创建Metal纹理时,使用对应10位的像素格式:

  • Y平面(kCVPixelFormatType_10BitY)对应Metal格式:MTLPixelFormatR10Unorm
  • UV平面(kCVPixelFormatType_10BitCbCr)对应Metal格式:MTLPixelFormatRG10Unorm

2. 正确设置纹理的宽高参数

创建纹理时,直接从CVPixelBuffer获取平面的真实宽高:

// 获取Y平面的宽高
NSInteger yWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
NSInteger yHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
// UV平面的宽高
NSInteger uvWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
NSInteger uvHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);

// 创建Y纹理描述符
MTLTextureDescriptor *yDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR10Unorm width:yWidth height:yHeight mipmapped:NO];
yDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
MTLTexture *videoY = [device newTextureWithDescriptor:yDesc];
// 将CVPixelBuffer的Y平面内容复制到纹理
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
[videoY replaceRegion:MTLRegionMake2D(0, 0, yWidth, yHeight)
        mipmapLevel:0
          withBytes:CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)
        bytesPerRow:CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0)];
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);

3. 禁用纹理重复模式

确保纹理的wrapS和wrapT设置为MTLWrapModeClampToEdge,避免超出纹理范围的坐标被重复映射:

yDesc.wrapS = MTLWrapModeClampToEdge;
yDesc.wrapT = MTLWrapModeClampToEdge;
uvDesc.wrapS = MTLWrapModeClampToEdge;
uvDesc.wrapT = MTLWrapModeClampToEdge;

4. 辅助检查着色器逻辑

添加UV坐标边界检查,避免写入超出UV纹理范围的内容:

uint2 uvCoord = uint2(gid.x / 2, gid.y / 2);
if (uvCoord.x >= videoUV.get_width() || uvCoord.y >= videoUV.get_height()) return;

额外验证步骤

  1. 用GPU Capture工具确认Y/UV纹理的宽高和像素格式与设置一致;
  2. 单独渲染Overlay纹理,确认其显示正常无拉伸;
  3. 渲染未修改的原视频帧,排除源视频本身的拉伸问题。

内容的提问来源于stack exchange,提问作者Lars Rönnbäck

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 23:39:58