Swift 金属将 bgra8Unorm 纹理保存到 PNG 文件
Swift Metal save bgra8Unorm texture to PNG file
我有一个输出纹理的内核,它是一个有效的 MTLTexture 对象。我想将它保存到项目工作目录中的 png 文件中。应该怎么做?
纹理格式为.bgra8Unorm
,目标输出格式为PNG
。
纹理存储在 MTLTexture 对象中。
编辑:我使用的是 macOS XCode。
如果您的应用在 macOS 上使用 Metal,您需要做的第一件事就是确保 CPU 可以读取您的纹理数据。如果内核正在写入的纹理处于 .private
存储模式,这意味着您需要从纹理 blit(复制)到处于 .managed
模式的另一个纹理。如果您的纹理开始于 .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)
}
}
我有一个输出纹理的内核,它是一个有效的 MTLTexture 对象。我想将它保存到项目工作目录中的 png 文件中。应该怎么做?
纹理格式为.bgra8Unorm
,目标输出格式为PNG
。
纹理存储在 MTLTexture 对象中。
编辑:我使用的是 macOS XCode。
如果您的应用在 macOS 上使用 Metal,您需要做的第一件事就是确保 CPU 可以读取您的纹理数据。如果内核正在写入的纹理处于 .private
存储模式,这意味着您需要从纹理 blit(复制)到处于 .managed
模式的另一个纹理。如果您的纹理开始于 .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)
}
}