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

Swift Metal计算着色器混合异常:高斯纹理仅显示左上象限

问题原因与修复方案

核心问题分析

你遇到的模糊纹理仅显示左上象限的问题,根源在于线程调度用了视图逻辑尺寸而非纹理实际像素尺寸,以及直接读写同一纹理的不安全操作:

  1. 线程数量不足:iPhone XR是Retina屏幕(缩放因子2),view.bounds.size是逻辑坐标,而drawable.texture是实际像素尺寸(比如XR的纹理尺寸为828x1792,逻辑尺寸仅414x896)。用逻辑尺寸计算线程组数,导致仅调度了1/4的线程,只处理了纹理左上1/4区域。
  2. 纹理读写冲突:你将drawable.texture同时绑定为输入和输出,Metal不允许在同一命令缓冲内对同一纹理进行读写,会引发未定义行为。

修复步骤

1. 修正线程调度逻辑

始终基于drawable.texture的实际像素尺寸计算线程数,替换原代码中view.bounds的相关逻辑:

// 替换原else分支的线程调度代码
let textureWidth = drawable.texture.width
let textureHeight = drawable.texture.height

// 使用8x8的线程组(比1x1效率更高)
let threadGroupSize = MTLSize(width: 8, height: 8, depth: 1)

// 计算线程组数,确保覆盖整个纹理(避免尺寸无法整除的情况)
let numGroups = MTLSize(
    width: (textureWidth + threadGroupSize.width - 1) / threadGroupSize.width,
    height: (textureHeight + threadGroupSize.height - 1) / threadGroupSize.height,
    depth: 1
)

computeEncoder?.dispatchThreadgroups(numGroups, threadsPerThreadgroup: threadGroupSize)

或者用更简洁的dispatchThreads方式:

let threadsPerGroup = MTLSize(width: 8, height: 8, depth: 1)
let threadsPerGrid = MTLSize(width: textureWidth, height: textureHeight, depth: 1)
computeEncoder?.dispatchThreads(threadsPerGrid, threadsPerThreadgroup: threadsPerGroup)

2. 避免纹理读写冲突

创建临时纹理作为混合输出,不要直接修改drawable纹理:

// 混合前创建临时纹理
let descriptor = MTLTextureDescriptor()
descriptor.width = drawable.texture.width
descriptor.height = drawable.texture.height
descriptor.pixelFormat = drawable.texture.pixelFormat
descriptor.usage = [.shaderRead, .shaderWrite]
guard let tempTexture = device.makeTexture(descriptor: descriptor) else {
    fatalError("无法创建临时纹理")
}

// 绑定纹理时用临时纹理作为输出
computeEncoder?.setTexture(tempTexture, index: 2)

// 混合完成后,将临时纹理内容拷贝到drawable纹理
let blitEncoder = commandBuffer.makeBlitCommandEncoder()
blitEncoder?.copy(from: tempTexture, to: drawable.texture)
blitEncoder?.endEncoding()

3. 验证纹理尺寸一致性

确保模糊纹理self.debugImageA与drawable纹理尺寸完全匹配,可在绑定纹理前打印验证:

print("Drawable纹理尺寸: \(drawable.texture.width)x\(drawable.texture.height)")
print("模糊纹理尺寸: \(self.debugImageA.width)x\(self.debugImageA.height)")

修正后的完整代码片段

// 完成3D场景渲染
commandEncoder.endEncoding()

print("Before Texture: \(drawable.texture.width) \(drawable.texture.height)")
print("模糊纹理尺寸: \(self.debugImageA.width) \(self.debugImageA.height)")

// 创建临时纹理避免读写冲突
let descriptor = MTLTextureDescriptor()
descriptor.width = drawable.texture.width
descriptor.height = drawable.texture.height
descriptor.pixelFormat = drawable.texture.pixelFormat
descriptor.usage = [.shaderRead, .shaderWrite]
guard let tempTexture = device.makeTexture(descriptor: descriptor) else {
    fatalError("无法创建临时纹理")
}

// 创建并配置计算着色器
guard let computeFunction = self.computeLibrary.makeFunction(name: "ComputeBlender"),
      let computePipelineState = try? device.makeComputePipelineState(function: computeFunction),
      let computeEncoder = commandBuffer.makeComputeCommandEncoder() else {
    fatalError("计算管线初始化失败")
}

computeEncoder.setComputePipelineState(computePipelineState)
computeEncoder.setTexture(drawable.texture, index: 0)
computeEncoder.setTexture(self.debugImageA, index: 1)
computeEncoder.setTexture(tempTexture, index: 2)

// 基于实际纹理尺寸调度线程
let textureWidth = drawable.texture.width
let textureHeight = drawable.texture.height
let threadGroupSize = MTLSize(width: 8, height: 8, depth: 1)
let numGroups = MTLSize(
    width: (textureWidth + threadGroupSize.width - 1) / threadGroupSize.width,
    height: (textureHeight + threadGroupSize.height - 1) / threadGroupSize.height,
    depth: 1
)
computeEncoder.dispatchThreadgroups(numGroups, threadsPerThreadgroup: threadGroupSize)

computeEncoder.endEncoding()

// 将临时纹理内容拷贝到drawable
if let blitEncoder = commandBuffer.makeBlitCommandEncoder() {
    blitEncoder.copy(from: tempTexture, to: drawable.texture)
    blitEncoder.endEncoding()
}

// 捕获调试纹理
self.debugImageB = drawable.texture

// 呈现最终画面
commandBuffer.present(drawable)
commandBuffer.commit()
commandBuffer.waitUntilCompleted()

print("After Texture: \(self.debugImageB.width) \(self.debugImageB.height)")

着色器说明

如果不需要颜色通道交换效果,可去掉着色器中result_color = float4(result_color.g, result_color.b, result_color.r, 1.0);这一行。

内容的提问来源于stack exchange,提问作者PhilBot

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 07:45:38