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 值。
我正在开发一个支持 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 值。