我正在开发一个 MTKView 支持的绘画程序,该程序可以通过存储关键帧的 MTLTextures 数组重放绘画历史。我遇到了一个问题,有时这些 MTLTextures 的内容会被打乱。
例如,假设我想将下面的一部分图形存储为关键帧:
在播放过程中,有时绘图会完全按照预期显示,但有时会显示如下:
注意图片的扭曲部分。(未失真部分构成静态背景图像,它不是相关关键帧的一部分)
我在下面描述了我从 MTKView 的 currentDrawable 创建单个 MTLTextures 的方式。由于我不会讨论颜色深度问题,这个过程可能看起来有点迂回。
我首先得到构成关键帧的屏幕子部分的 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 存储在 volatile 数组中可能是不明智的,但我不知道这一点。我只是认为使用 MTLTextures 将是更新内容的最快方法。
顺便说一句,当我将关键帧数组换成 UIImage.pngData 数组时,我没有显示问题,但速度要慢得多。从好的方面来说,它告诉我从 currentDrawable 到 keyframeCGImage 的初始捕获工作正常。
任何想法将不胜感激。
ps根据反馈添加一些细节:
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)
CGImageToMTL纹理:
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)