Swift Metal 将 bgra8Unorm 纹理保存到 PNG 文件

mac*_*e21 4 png textures swift metal metalkit

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

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

编辑:我在 macOS XCode 上。

war*_*enm 7

如果你的应用在 macOS 上使用 Metal,你需要做的第一件事就是确保你的纹理数据可以被 CPU 读取。如果内核正在写入的纹理处于.private存储模式,则意味着您需要从纹理 blit(复制)到.managed模式中的另一个纹理。如果您的纹理从.managed存储开始,您可能需要创建一个 blit 命令编码器并调用synchronize(resource:)该纹理以确保其在 GPU 上的内容反映在 CPU 上:

if let blitEncoder = commandBuffer.makeBlitCommandEncoder() {
    blitEncoder.synchronize(resource: outputTexture)
    blitEncoder.endEncoding()
}
Run Code Online (Sandbox Code Playgroud)

一旦命令缓冲区完成(您可以通过调用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
}
Run Code Online (Sandbox Code Playgroud)

您会注意到在此函数中间调用了一个名为 的实用程序函数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))
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以编写一个函数,使我们能够将纹理写入指定的 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)
    }
}
Run Code Online (Sandbox Code Playgroud)