代码之家  ›  专栏  ›  技术社区  ›  mackycheese21

swift metal save bgra8unorm纹理到png文件

  •  0
  • mackycheese21  · 技术社区  · 6 年前

    我有一个输出纹理的内核,它是一个有效的mtltexture对象。我想将它保存到我的项目工作目录中的PNG文件中。怎么做?

    纹理格式为 .bgra8Unorm ,目标输出格式为 PNG . 纹理存储在mtltexture对象中。

    编辑:我在MacOS Xcode上。

    1 回复  |  直到 6 年前
        1
  •  1
  •   warrenm    6 年前

    如果你的应用程序在MacOS上使用金属,你需要做的第一件事就是确保你的纹理数据可以被CPU读取。如果内核正在写入的纹理 .private 存储模式,这意味着您需要在 .managed 模式。如果你的质地开始 管理的 存储,您可能需要创建一个blit命令编码器并调用 synchronize(resource:) 在纹理上确保其在GPU上的内容反映在CPU上:

    if let blitEncoder = commandBuffer.makeBlitCommandEncoder() {
        blitEncoder.synchronize(resource: outputTexture)
        blitEncoder.endEncoding()
    }
    

    一旦命令缓冲区完成(您可以通过调用 waitUntilCompleted 或者通过向命令缓冲区添加一个完成处理程序),就可以复制数据并创建图像:

    func makeImage(for texture: MTLTexture) -> CGImage? {
        assert(texture.pixelFormat == .bgra8Unorm)
    
        let width = texture.width
        let height = texture.height
        let pixelByteCount = 4 * MemoryLayout<UInt8>.size
        let imageBytesPerRow = width * pixelByteCount
        let imageByteCount = imageBytesPerRow * height
        let imageBytes = UnsafeMutableRawPointer.allocate(byteCount: imageByteCount, alignment: pixelByteCount)
        defer {
            imageBytes.deallocate()
        }
    
        texture.getBytes(imageBytes,
                         bytesPerRow: imageBytesPerRow,
                         from: MTLRegionMake2D(0, 0, width, height),
                         mipmapLevel: 0)
    
        swizzleBGRA8toRGBA8(imageBytes, width: width, height: height)
    
        guard let colorSpace = CGColorSpace(name: CGColorSpace.linearSRGB) else { return nil }
        let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
        guard let bitmapContext = CGContext(data: nil,
                                            width: width,
                                            height: height,
                                            bitsPerComponent: 8,
                                            bytesPerRow: imageBytesPerRow,
                                            space: colorSpace,
                                            bitmapInfo: bitmapInfo) else { return nil }
        bitmapContext.data?.copyMemory(from: imageBytes, byteCount: imageByteCount)
        let image = bitmapContext.makeImage()
        return image
    }
    

    您会注意到在这个函数的中间有一个调用,调用了一个实用函数 swizzleBGRA8toRGBA8 . 此函数交换图像缓冲区中的字节,使它们按照coregraphics预期的rgba顺序排列。它使用Vimage(一定要 import Accelerate )看起来是这样的:

    func swizzleBGRA8toRGBA8(_ bytes: UnsafeMutableRawPointer, width: Int, height: Int) {
        var sourceBuffer = vImage_Buffer(data: bytes,
                                         height: vImagePixelCount(height),
                                         width: vImagePixelCount(width),
                                         rowBytes: width * 4)
        var destBuffer = vImage_Buffer(data: bytes,
                                       height: vImagePixelCount(height),
                                       width: vImagePixelCount(width),
                                       rowBytes: width * 4)
        var swizzleMask: [UInt8] = [ 2, 1, 0, 3 ] // BGRA -> RGBA
        vImagePermuteChannels_ARGB8888(&sourceBuffer, &destBuffer, &swizzleMask, vImage_Flags(kvImageNoFlags))
    }
    

    现在,我们可以编写一个函数,使我们能够将纹理写入指定的URL:

    func writeTexture(_ texture: MTLTexture, url: URL) {
        guard let image = makeImage(for: texture) else { return }
    
        if let imageDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypePNG, 1, nil) {
            CGImageDestinationAddImage(imageDestination, image, nil)
            CGImageDestinationFinalize(imageDestination)
        }
    }