如何将 16 位图像加载到 Metal 纹理中?
How to load 16-bit images into Metal textures?
使用 MTKTextureLoader.newTexture
的推荐方法不适用于 16 位图像。
named:
版本自动将图像转换为 8 位像素格式
cgImage:
版本以 Image decoding failed
结束
UIImage 和 NSImage 都支持加载 16 位图像,并且有它们方便的 .cgimage
方法,可以在一个衬里转换为 CGImage,因此在两个平台上获得 CGImage 都解决了。
我如何编写一个函数来转换 CGImage 和 returns 16 位金属纹理?
下面的 loadEXRTexture
函数加载 extended-range 图像并将其像素转换为 half-precision floating-point,将生成的像素数据存储到 MTLTexture
中.rgba16Float
格式。它试图通过使用 CGImageSource
选项来保留 floating-point 图像的原始范围,这些选项改变了 Quartz 默认值的行为(它应用 "decode" 函数将源数据压缩到合适的范围用于绘制到位图上下文中)。它假设图像源创建的图像具有三个 floating-point 分量,以 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
}
您需要将此实用程序包含在桥接 header 中,因为据我所知,Swift 没有 __fp16
类型或任何等效类型:
#include <stdint.h>
static inline void storeAsF16(float value, uint16_t *pointer) { *(__fp16 *)pointer = value; }
使用 MTKTextureLoader.newTexture
的推荐方法不适用于 16 位图像。
named:
版本自动将图像转换为 8 位像素格式cgImage:
版本以Image decoding failed
结束
UIImage 和 NSImage 都支持加载 16 位图像,并且有它们方便的 .cgimage
方法,可以在一个衬里转换为 CGImage,因此在两个平台上获得 CGImage 都解决了。
我如何编写一个函数来转换 CGImage 和 returns 16 位金属纹理?
下面的 loadEXRTexture
函数加载 extended-range 图像并将其像素转换为 half-precision floating-point,将生成的像素数据存储到 MTLTexture
中.rgba16Float
格式。它试图通过使用 CGImageSource
选项来保留 floating-point 图像的原始范围,这些选项改变了 Quartz 默认值的行为(它应用 "decode" 函数将源数据压缩到合适的范围用于绘制到位图上下文中)。它假设图像源创建的图像具有三个 floating-point 分量,以 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
}
您需要将此实用程序包含在桥接 header 中,因为据我所知,Swift 没有 __fp16
类型或任何等效类型:
#include <stdint.h>
static inline void storeAsF16(float value, uint16_t *pointer) { *(__fp16 *)pointer = value; }