【问题标题】:Swift Metal save bgra8Unorm texture to PNG fileSwift Metal 将 bgra8Unorm 纹理保存到 PNG 文件
【发布时间】:2019-03-26 00:09:03
【问题描述】:

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

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

编辑:我在 macOS XCode 上。

【问题讨论】:

    标签: swift png textures metal metalkit


    【解决方案1】:

    如果您的应用在 macOS 上使用 Metal,您需要做的第一件事是确保 CPU 可以读取您的纹理数据。如果内核正在写入的纹理处于.private 存储模式,这意味着您需要在.managed 模式下将纹理从纹理blit(复制)到另一个纹理。如果您的纹理开始于 .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)
        }
    }
    

    【讨论】:

    • 很奇怪。它输出一个大小合适但全白的 PNG 图像。这可能是我的内核代码的问题吗?
    • 可能。当您将纯色(例如红色)写入内核中的每个像素时会发生什么?
    • 我试过了,但它什么也没做 - 仍然是一个空白的白色背景。准备纹理以供计算内核写入时有什么特别需要做的吗?
    • 它的用法应该在创建时设置为.shaderWrite,否则,不是真的。您能否确认其存储模式设置为.managed,并且您正在等待包含 blit 同步命令的命令缓冲区完成,然后再尝试回读?
    • 如果在getBytes 行设置断点并快速查看纹理对象,它是否有预期的内容?如果不是,那么问题在于您如何调度内核。如果是这样,问题可能出在我的某个函数中(前提是您正确同步)。
    猜你喜欢
    • 2018-06-09
    • 1970-01-01
    • 2016-09-26
    • 2021-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-09
    相关资源
    最近更新 更多