Если ваше приложение использует Metal в macOS, первое, что вам нужно сделать, - это убедиться, что ваши данные текстуры могут быть прочитаны процессором.Если текстура, которую пишет ядро, находится в режиме хранения .private
, это означает, что вам нужно будет скопировать (скопировать) текстуру в другую текстуру в режиме .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
.Эта функция заменяет байты в буфере изображения так, чтобы они были в порядке RGBA, ожидаемом CoreGraphics.Он использует 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)
}
}