Swift Metal сохранить bgra8Unorm текстуры в файл PNG - PullRequest
0 голосов
/ 22 октября 2018

У меня есть ядро, которое выводит текстуру, и это действительный объект MTLTexture.Я хочу сохранить его в png-файл в рабочем каталоге моего проекта.Как это сделать?

Формат текстуры .bgra8Unorm, а целевой формат вывода PNG.Текстура хранится в объекте MTLTexture.

РЕДАКТИРОВАТЬ: Я на MacOS XCode.

1 Ответ

0 голосов
/ 22 октября 2018

Если ваше приложение использует 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)
    }
}
...