MTKView 经常显示混乱的 MTLTextures

MTKView frequently displaying scrambled MTLTextures

我正在开发一个支持 MTKView 的绘画程序,它可以通过存储关键帧的 MTLTextures 数组重播绘画历史。我遇到一个问题,有时这些 MTLTextures 的内容会被打乱。

举个例子,假设我想将下面绘图的一部分存储为关键帧:

在播放过程中,有时绘图会完全按照预期显示,但有时会显示如下:

注意图片的变形部分。 (未失真部分构成静态背景图像,不属于相关关键帧)

我在下面描述了我从 MTKView 的 currentDrawable 创建单独的 MTLTexture 的方式。由于颜色深度的问题我就不多说了,这个过程可能看起来有点迂回。

我首先得到一个构成关键帧的屏幕分段的CGImage。
我使用该 CGImage 创建一个绑定到 MTKView 设备的 MTLTexture。 我将该 MTLTexture 存储到 MTLTextureStructure 中,该 MTLTextureStructure 存储 MTLTexture 和关键帧的边界框(我稍后需要) 最后,我存储在 MTLTextureStructures (keyframeMetalArray) 数组中。在播放过程中,当我点击一个关键帧时,我会从这个 keyframeMetalArray 中获取它。

相关代码概述如下。

let keyframeCGImage = weakSelf!.canvasMetalViewPainting.mtlTextureToCGImage(bbox: keyframeBbox, copyMode: copyTextureMode.textureKeyframe) // convert from MetalTexture to CGImage

let keyframeMTLTexture = weakSelf!.canvasMetalViewPainting.CGImageToMTLTexture(cgImage: keyframeCGImage)

let keyframeMTLTextureStruc = mtlTextureStructure(texture: keyframeMTLTexture, bbox: keyframeBbox, strokeType: brushTypeMode.brush)

weakSelf!.keyframeMetalArray.append(keyframeMTLTextureStruc)

我没有提供关于每次转换如何发生的细节,我想知道从架构设计的角度来看,我是否忽略了一些正在破坏我存储在 keyframeMetalArray 中的数据的东西。尝试将这些 MTLTextures 存储在易失性数组中可能是不明智的,但我不知道这是事实。我只是认为使用 MTLTextures 是更新内容的最快方式。

顺便说一下,当我将关键帧数组换成 UIImage.pngData 数组时,我没有显示问题,但速度要慢得多。从好的方面来说,它告诉我从 currentDrawable 到 keyframeCGImage 的初始捕获工作正常。

如有任何想法,我们将不胜感激。

p.s。根据反馈添加一些细节:

mtlTextureToCGImage:

func mtlTextureToCGImage(bbox: CGRect, copyMode: copyTextureMode) -> CGImage {

    let kciOptions = [convertFromCIContextOption(CIContextOption.outputPremultiplied): true,
                      convertFromCIContextOption(CIContextOption.useSoftwareRenderer): false] as [String : Any]
    let bboxStrokeScaledFlippedY = CGRect(x: (bbox.origin.x * self.viewContentScaleFactor), y: ((self.viewBounds.height - bbox.origin.y - bbox.height) * self.viewContentScaleFactor), width: (bbox.width * self.viewContentScaleFactor), height: (bbox.height * self.viewContentScaleFactor))

let strokeCIImage = CIImage(mtlTexture: metalDrawableTextureKeyframe,
                                  options: convertToOptionalCIImageOptionDictionary(kciOptions))!.oriented(CGImagePropertyOrientation.downMirrored)
      let imageCropCG = cicontext.createCGImage(strokeCIImage, from: bboxStrokeScaledFlippedY, format: CIFormat.RGBA8, colorSpace: colorSpaceGenericRGBLinear)

      cicontext.clearCaches()

      return imageCropCG!

} // end of func mtlTextureToCGImage(bbox: CGRect)

CGImageToMTLTexture:

func CGImageToMTLTexture (cgImage: CGImage) -> MTLTexture {

    // Note that we forego the more direct method of creating stampTexture:
    //let stampTexture = try! MTKTextureLoader(device: self.device!).newTexture(cgImage: strokeUIImage.cgImage!, options: nil)
    // because  MTKTextureLoader seems to be doing additional processing which messes with the resulting texture/colorspace

    let width = Int(cgImage.width)
    let height = Int(cgImage.height)


    let bytesPerPixel = 4

    let rowBytes = width * bytesPerPixel
    //
    let texDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm,
                                                                 width: width,
                                                                 height: height,
                                                                 mipmapped: false)
    texDescriptor.usage = MTLTextureUsage(rawValue: MTLTextureUsage.shaderRead.rawValue)
    texDescriptor.storageMode = .shared
    guard let stampTexture = device!.makeTexture(descriptor: texDescriptor) else {
      return brushTextureSquare // return SOMETHING

    }

    let dstData: CFData = (cgImage.dataProvider!.data)!
    let pixelData = CFDataGetBytePtr(dstData)

    let region = MTLRegionMake2D(0, 0, width, height)

    print ("[MetalViewPainting]: w= \(width) | h= \(height)  region = \(region.size)")

    stampTexture.replace(region: region, mipmapLevel: 0, withBytes: pixelData!, bytesPerRow: Int(rowBytes))

    return stampTexture

  } // end of func CGImageToMTLTexture (cgImage: CGImage)

这种失真看起来像是 CGImage 和 MTLTexture 之间的每行字节对齐问题。当您的图像大小超出 MTLDevice 的每行字节对齐要求时,您可能只会看到此问题。如果您确实需要将纹理存储为 CGImage,请确保在复制回纹理时使用 CGImage 的 bytesPerRow 值。