Search code examples
swiftxcodemetal

How to programmatically export any MTLTexture to KTX in Swift


Detail

We can export the raw texture data including other layers or slices in KTX format after we capture the frame by the frame capture.

enter image description here

After that, we can import by using MTKTextureLoader.

let texture = try! await loader.newTexture(URL: Bundle.main.url(forResource: "texture", withExtension: "ktx")!)

Goal

But how to programmatically export the MTLTexture to KTX format in Swift?


Solution

  • You can check ImageIO whether ImageIO supports the format you want to export to:

    Call CGImageDestinationCopyTypeIdentifiers() from ImageIO module to get a list of all the UTI that the CGImageDestination supports.

    For example, these are the types ImageIO supports on my machine:

    (
        "public.jpeg",
        "public.png",
        "com.compuserve.gif",
        "public.tiff",
        "public.jpeg-2000",
        "com.apple.atx",
        "org.khronos.ktx",
        "org.khronos.ktx2",
        "org.khronos.astc",
        "com.microsoft.dds",
        "public.heic",
        "public.heics",
        "com.microsoft.ico",
        "com.microsoft.bmp",
        "com.apple.icns",
        "com.adobe.photoshop-image",
        "com.adobe.pdf",
        "com.truevision.tga-image",
        "com.ilm.openexr-image",
        "public.pbm",
        "public.pvr"
    )
    

    Then, use CGImage to output it:

    let texture: MTLTexture = ... // input texture
    let ciContext = CIContext(mtlDevice: texture.device)
    let ciImage = CIImage(mtlTexture: texture, options: nil) // you might need to add `.oriented(.downMirrored)` here
    let colorspace = CGColorSpace(name: CGColorSpace.sRGB)
    let cgImage = ciContext.createCGImage(ciImage, from: .init(origin: .zero, size: .init(width: texture.width, height: texture.height)), format: .RGBA8, colorSpace: colorspace)!
    
    let uniformType: UTType = ... // uniform type from CGImageDestinationCopyTypeIdentifiers
    let outputURL: URL = ... // the url
    
    try FileManager.default.createDirectory(at: outputURL.deletingLastPathComponent(), withIntermediateDirectories: true) // you might needs this to create intermediate directories
    
    let destination = CGImageDestinationCreateWithURL(outputURL as CFURL, uniformType.identifier as CFString, 1, nil)!
    CGImageDestinationAddImage(destination, cgImage, nil)
    let imageOutputSuccessfully = CGImageDestinationFinalize(destination)