【问题标题】:How to load 16-bit images into Metal textures?如何将 16 位图像加载到金属纹理中?
【发布时间】:2018-07-30 00:50:27
【问题描述】:

使用MTKTextureLoader.newTexture 的推荐方法不适用于16 位图像。

  1. named: 版本以静默方式将图像转换为 8 位像素格式
  2. cgImage: 版本以Image decoding failed 终止

UIImage 和 NSImage 都支持加载 16 位图像,并且有它们方便的 .cgimage 方法,它可以在一行中转换为 CGImage,因此可以解决在两个平台上获取 CGImage 的问题。

如何编写转换 CGImage 并返回 16 位金属纹理的函数?

【问题讨论】:

    标签: swift cocoa cocoa-touch metal


    【解决方案1】:

    下面的loadEXRTexture 函数加载一个扩展范围的图像并将其像素转换为半精度浮点,将生成的像素数据存储为MTLTexture 格式为.rgba16Float。它试图通过使用CGImageSource 选项来保留浮点图像的原始范围,这些选项改变了 Quartz 默认的行为(它应用了一个“解码”函数,将源数据压缩到适合绘制到位图上下文的范围内) )。它假设图像源创建的图像具有三个按 RGB 顺序打包的浮点分量。

    func convertRGBF32ToRGBAF16(_ src: UnsafePointer<Float>, _ dst: UnsafeMutablePointer<UInt16>, pixelCount: Int) {
        for i in 0..<pixelCount {
            storeAsF16(src[i * 3 + 0], dst + (i * 4) + 0)
            storeAsF16(src[i * 3 + 1], dst + (i * 4) + 1)
            storeAsF16(src[i * 3 + 2], dst + (i * 4) + 2)
            storeAsF16(1.0, dst + (i * 4) + 3)
        }
    }
    
    func loadEXRTexture(_ url: URL, device: MTLDevice) -> MTLTexture? {
        guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) else { return nil }
    
        let options = [ kCGImageSourceShouldCache : true, kCGImageSourceShouldAllowFloat : true ] as CFDictionary
        guard let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options) else { return nil }
    
        let descriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba16Float,
                                                                  width: image.width,
                                                                  height: image.height,
                                                                  mipmapped: false)
        descriptor.usage = .shaderRead
        guard let texture = device.makeTexture(descriptor: descriptor) else { return nil }
    
        if image.bitsPerComponent == 32 && image.bitsPerPixel == 96 {
            let srcData: CFData! = image.dataProvider?.data
            CFDataGetBytePtr(srcData).withMemoryRebound(to: Float.self, capacity: image.width * image.height * 3) { srcPixels in
                let dstPixels = UnsafeMutablePointer<UInt16>.allocate(capacity: 4 * image.width * image.height)
                convertRGBF32ToRGBAF16(srcPixels, dstPixels, pixelCount: image.width * image.height)
                texture.replace(region: MTLRegionMake2D(0, 0, image.width, image.height),
                                mipmapLevel: 0,
                                withBytes: dstPixels,
                                bytesPerRow: MemoryLayout<UInt16>.size * 4 * image.width)
                dstPixels.deallocate()
            }
        }
    
        return texture
    }
    

    您需要将此实用程序包含在桥接头中,因为据我所知,Swift 没有 __fp16 类型或任何等效类型:

    #include <stdint.h>
    static inline void storeAsF16(float value, uint16_t *pointer) { *(__fp16 *)pointer = value; }
    

    【讨论】:

    • 使用.cgimage 获取 CGImage 对 NSImage 和 UIImage 来说只是一个行,所以这很好。不清楚的是如何将 CGImage 数据转换为 MTLTexture。我已经更新了我的问题。
    • 图片有什么色彩空间?我担心你可能会得到一个已经压缩了其组件的图像(这是 Quartz 所做的by default)。颜色空间有bpc = 16kCGImageByteOrder16Littlebpc = 32kCGImageByteOrder32Little | kCGBitmapFloatComponents;还是其他组合?
    • 根据实际问题重写了我的答案。
    • 哇,你居然为 EXR 做了 Float 版本,我已经放弃了,但它太有价值了!我只是在考虑 UInt16 的简单 16 位 PNG 图像。对于 UInt16,我实际上根据您的代码弄清楚了如何做到这一点:github.com/warrenm/slug-swift-metal/blob/master/SwiftMetalDemo/… 但我无法让它与 Float 一起使用,因为它给了我疯狂的小值,比如 -e17 等。我会检查你的版本。
    猜你喜欢
    • 2020-10-29
    • 2021-11-08
    • 1970-01-01
    • 1970-01-01
    • 2018-02-16
    • 2018-04-25
    • 2018-10-15
    • 1970-01-01
    • 2020-09-08
    相关资源
    最近更新 更多