Article / 2026/05/29
MTKTextureLoader API 完整指南
MTKTextureLoader 是 MetalKit 框架中的核心类,专门用于从各种数据源(图片文件、CGImage、数据等)创建 MTLTexture 对象。它简化了纹理加载过程,支持多种图像格式,并提供了丰富的配置选项。
🚀 MTKTextureLoader API 完整指南
概述
MTKTextureLoader 是 MetalKit 框架中的核心类,专门用于从各种数据源(图片文件、CGImage、数据等)创建 MTLTexture 对象。它简化了纹理加载过程,支持多种图像格式,并提供了丰富的配置选项。
初始化方法
基本初始化
// 使用 MTLDevice 初始化
let textureLoader = MTKTextureLoader(device: metalDevice)
核心加载方法
1. 从 CGImage 创建纹理
// 同步方法
func newTexture(cgImage: CGImage, options: [MTKTextureLoader.Option : Any]?) throws -> MTLTexture
// 异步方法
func newTexture(cgImage: CGImage, options: [MTKTextureLoader.Option : Any]?, completionHandler: @escaping MTKTextureLoader.Callback)
// 使用示例
let textureLoader = MTKTextureLoader(device: device)
let options: [MTKTextureLoader.Option: Any] = [
.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue),
.textureStorageMode: NSNumber(value: MTLStorageMode.shared.rawValue)
]
do {
let texture = try textureLoader.newTexture(cgImage: cgImage, options: options)
// 使用纹理
} catch {
print("纹理加载失败: \(error)")
}
2. 从 URL 加载纹理
// 同步方法
func newTexture(URL: URL, options: [MTKTextureLoader.Option : Any]?) throws -> MTLTexture
// 异步方法
func newTexture(URL: URL, options: [MTKTextureLoader.Option : Any]?, completionHandler: @escaping MTKTextureLoader.Callback)
// 使用示例
let imageURL = Bundle.main.url(forResource: "texture", withExtension: "jpg")!
textureLoader.newTexture(URL: imageURL, options: options) { (texture, error) in
if let texture = texture {
print("纹理加载成功: \(texture.width)x\(texture.height)")
} else if let error = error {
print("纹理加载失败: \(error)")
}
}
3. 从文件名加载纹理
// 同步方法
func newTexture(name: String, scaleFactor: CGFloat, bundle: Bundle?, options: [MTKTextureLoader.Option : Any]?) throws -> MTLTexture
// 异步方法
func newTexture(name: String, scaleFactor: CGFloat, bundle: Bundle?, options: [MTKTextureLoader.Option : Any]?, completionHandler: @escaping MTKTextureLoader.Callback)
// 使用示例
do {
let texture = try textureLoader.newTexture(
name: "texture.jpg",
scaleFactor: 1.0,
bundle: Bundle.main,
options: options
)
} catch {
print("加载失败: \(error)")
}
4. 从 Data 创建纹理
// 同步方法
func newTexture(data: Data, options: [MTKTextureLoader.Option : Any]?) throws -> MTLTexture
// 异步方法
func newTexture(data: Data, options: [MTKTextureLoader.Option : Any]?, completionHandler: @escaping MTKTextureLoader.Callback)
// 使用示例
let imageData = Data() // 你的图像数据
do {
let texture = try textureLoader.newTexture(data: imageData, options: options)
} catch {
print("从数据创建纹理失败: \(error)")
}
5. 创建纹理数组
// 从 CGImage 数组创建
func newTextures(cgImages: [CGImage], options: [MTKTextureLoader.Option : Any]?) throws -> [MTLTexture]
// 从 URL 数组创建
func newTextures(URLs: [URL], options: [MTKTextureLoader.Option : Any]?) throws -> [MTLTexture]
// 异步版本
func newTextures(cgImages: [CGImage], options: [MTKTextureLoader.Option : Any]?, completionHandler: @escaping ([MTLTexture]?, Error?) -> Void)
// 使用示例
let cgImages: [CGImage] = [image1, image2, image3]
do {
let textures = try textureLoader.newTextures(cgImages: cgImages, options: options)
print("创建了 \(textures.count) 个纹理")
} catch {
print("批量创建纹理失败: \(error)")
}
MTKTextureLoader.Option 配置选项
纹理使用方式
// 纹理用途
.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue)
// 可用的纹理用途:
// - .shaderRead: 着色器读取
// - .shaderWrite: 着色器写入
// - .renderTarget: 渲染目标
// - .pixelFormatView: 像素格式视图
存储模式
// 存储模式
.textureStorageMode: NSNumber(value: MTLStorageMode.shared.rawValue)
// 可用的存储模式:
// - .shared: CPU 和 GPU 共享内存
// - .managed: 系统管理内存同步(仅 macOS)
// - .private: GPU 专用内存
// - .memoryless: 无内存存储(仅渲染目标)
CPU 缓存模式
// CPU 缓存模式
.textureCPUCacheMode: NSNumber(value: MTLCPUCacheMode.defaultCache.rawValue)
// 可用的缓存模式:
// - .defaultCache: 默认缓存
// - .writeCombined: 写合并缓存
坐标系和方向
// 纹理原点位置
.origin: MTKTextureLoader.Origin.topLeft
// 可用选项:
// - .topLeft: 左上角为原点
// - .bottomLeft: 左下角为原点(OpenGL 风格)
// 垂直翻转
.generateMipmaps: NSNumber(value: true)
Mipmap 生成
// 自动生成 mipmap
.generateMipmaps: NSNumber(value: true)
// SRGB 处理
.SRGB: NSNumber(value: true)
立方体贴图
// 加载为立方体贴图
.cubeLayout: MTKTextureLoader.CubeLayout.vertical
// 立方体贴图布局选项:
// - .vertical: 垂直排列
// - .horizontal: 水平排列
分配器
// 自定义分配器
.allocateMipmaps: NSNumber(value: true)
完整的配置示例
基本 2D 纹理加载
let options: [MTKTextureLoader.Option: Any] = [
.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue),
.textureStorageMode: NSNumber(value: MTLStorageMode.shared.rawValue),
.origin: MTKTextureLoader.Origin.topLeft,
.generateMipmaps: NSNumber(value: true),
.SRGB: NSNumber(value: false)
]
do {
let texture = try textureLoader.newTexture(cgImage: cgImage, options: options)
print("纹理创建成功: \(texture.width)x\(texture.height), mipmap级别: \(texture.mipmapLevelCount)")
} catch {
print("纹理创建失败: \(error)")
}
HDR/高动态范围纹理
let hdrOptions: [MTKTextureLoader.Option: Any] = [
.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue),
.textureStorageMode: NSNumber(value: MTLStorageMode.private.rawValue),
.origin: MTKTextureLoader.Origin.topLeft,
.SRGB: NSNumber(value: false), // HDR 通常不使用 sRGB
.generateMipmaps: NSNumber(value: false) // HDR 环境贴图通常不需要 mipmap
]
立方体环境贴图
let cubeMapOptions: [MTKTextureLoader.Option: Any] = [
.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue),
.textureStorageMode: NSNumber(value: MTLStorageMode.private.rawValue),
.cubeLayout: MTKTextureLoader.CubeLayout.vertical,
.origin: MTKTextureLoader.Origin.topLeft,
.generateMipmaps: NSNumber(value: true)
]
do {
let cubeTexture = try textureLoader.newTexture(
name: "skybox_cube.jpg",
scaleFactor: 1.0,
bundle: Bundle.main,
options: cubeMapOptions
)
print("立方体贴图创建成功, 类型: \(cubeTexture.textureType)")
} catch {
print("立方体贴图创建失败: \(error)")
}
实际应用示例
游戏纹理加载管理器
class TextureManager {
private let device: MTLDevice
private let textureLoader: MTKTextureLoader
private var textureCache: [String: MTLTexture] = [:]
init(device: MTLDevice) {
self.device = device
self.textureLoader = MTKTextureLoader(device: device)
}
func loadTexture(named name: String, options: [MTKTextureLoader.Option: Any]? = nil) -> MTLTexture? {
// 检查缓存
if let cachedTexture = textureCache[name] {
return cachedTexture
}
let defaultOptions: [MTKTextureLoader.Option: Any] = [
.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue),
.textureStorageMode: NSNumber(value: MTLStorageMode.shared.rawValue),
.origin: MTKTextureLoader.Origin.topLeft,
.generateMipmaps: NSNumber(value: true)
]
let finalOptions = options ?? defaultOptions
do {
let texture = try textureLoader.newTexture(
name: name,
scaleFactor: 1.0,
bundle: Bundle.main,
options: finalOptions
)
// 缓存纹理
textureCache[name] = texture
return texture
} catch {
print("加载纹理 '\(name)' 失败: \(error)")
return nil
}
}
func loadTextureAsync(named name: String, completion: @escaping (MTLTexture?) -> Void) {
if let cachedTexture = textureCache[name] {
completion(cachedTexture)
return
}
let options: [MTKTextureLoader.Option: Any] = [
.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue),
.textureStorageMode: NSNumber(value: MTLStorageMode.shared.rawValue),
.origin: MTKTextureLoader.Origin.topLeft
]
textureLoader.newTexture(name: name, scaleFactor: 1.0, bundle: Bundle.main, options: options) { [weak self] texture, error in
DispatchQueue.main.async {
if let texture = texture {
self?.textureCache[name] = texture
completion(texture)
} else {
print("异步加载纹理失败: \(error?.localizedDescription ?? "未知错误")")
completion(nil)
}
}
}
}
}
支持不同格式的纹理加载
extension MTKTextureLoader {
func loadTexture(from url: URL) -> MTLTexture? {
let fileExtension = url.pathExtension.lowercased()
var options: [MTKTextureLoader.Option: Any] = [
.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue),
.textureStorageMode: NSNumber(value: MTLStorageMode.shared.rawValue),
.origin: MTKTextureLoader.Origin.topLeft
]
// 根据文件格式调整选项
switch fileExtension {
case "hdr", "exr":
// HDR 格式
options[.SRGB] = NSNumber(value: false)
options[.generateMipmaps] = NSNumber(value: false)
case "jpg", "jpeg":
// JPEG 通常是 sRGB
options[.SRGB] = NSNumber(value: true)
options[.generateMipmaps] = NSNumber(value: true)
case "png":
// PNG 可能包含透明度
options[.SRGB] = NSNumber(value: true)
options[.generateMipmaps] = NSNumber(value: true)
default:
break
}
do {
return try newTexture(URL: url, options: options)
} catch {
print("加载纹理失败: \(error)")
return nil
}
}
}
纹理压缩和优化
class OptimizedTextureLoader {
private let device: MTLDevice
private let textureLoader: MTKTextureLoader
init(device: MTLDevice) {
self.device = device
self.textureLoader = MTKTextureLoader(device: device)
}
func loadCompressedTexture(named name: String) -> MTLTexture? {
// 根据设备能力选择最佳选项
var options: [MTKTextureLoader.Option: Any] = [
.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue),
.origin: MTKTextureLoader.Origin.topLeft
]
// 根据设备选择存储模式
if device.hasUnifiedMemory {
options[.textureStorageMode] = NSNumber(value: MTLStorageMode.shared.rawValue)
} else {
options[.textureStorageMode] = NSNumber(value: MTLStorageMode.private.rawValue)
}
// 移动设备优化
#if os(iOS)
options[.textureCPUCacheMode] = NSNumber(value: MTLCPUCacheMode.writeCombined.rawValue)
#endif
do {
return try textureLoader.newTexture(
name: name,
scaleFactor: UIScreen.main.scale,
bundle: Bundle.main,
options: options
)
} catch {
print("加载优化纹理失败: \(error)")
return nil
}
}
}
错误处理和调试
常见错误类型
func handleTextureLoadingError(_ error: Error) {
if let mtlError = error as? MTLLibraryError {
print("Metal 库错误: \(mtlError)")
} else if error.localizedDescription.contains("unsupported") {
print("不支持的图像格式")
} else if error.localizedDescription.contains("memory") {
print("内存不足")
} else {
print("未知纹理加载错误: \(error)")
}
}
纹理信息调试
func debugTexture(_ texture: MTLTexture) {
print("纹理信息:")
print("- 尺寸: \(texture.width) x \(texture.height)")
print("- 深度: \(texture.depth)")
print("- 类型: \(texture.textureType)")
print("- 像素格式: \(texture.pixelFormat)")
print("- Mipmap 级别: \(texture.mipmapLevelCount)")
print("- 数组长度: \(texture.arrayLength)")
print("- 用途: \(texture.usage)")
print("- 存储模式: \(texture.storageMode)")
}
性能优化建议
1. 异步加载
// 在后台队列加载大纹理
func loadLargeTextureAsync(url: URL, completion: @escaping (MTLTexture?) -> Void) {
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
do {
let texture = try self?.textureLoader.newTexture(URL: url, options: nil)
DispatchQueue.main.async {
completion(texture)
}
} catch {
DispatchQueue.main.async {
completion(nil)
}
}
}
}
2. 纹理缓存
// 使用 NSCache 进行智能缓存管理
private let textureCache = NSCache<NSString, MTLTexture>()
func cachedTexture(for name: String) -> MTLTexture? {
if let cached = textureCache.object(forKey: name as NSString) {
return cached
}
if let texture = loadTexture(named: name) {
textureCache.setObject(texture, forKey: name as NSString)
return texture
}
return nil
}
3. 内存管理
// 清理纹理缓存
func clearTextureCache() {
textureCache.removeAllObjects()
}
// 监听内存警告
NotificationCenter.default.addObserver(
forName: UIApplication.didReceiveMemoryWarningNotification,
object: nil,
queue: .main
) { _ in
clearTextureCache()
}
注意事项
- 线程安全性:MTKTextureLoader 是线程安全的,可以在多个线程中同时使用
- 内存管理:大纹理会占用大量内存,注意及时释放不用的纹理
- 格式支持:不是所有图像格式都被支持,需要处理加载失败的情况
- 设备兼容性:某些纹理格式和选项可能不被所有设备支持
- 性能考虑:同步加载可能会阻塞主线程,大文件建议使用异步加载
相关类型
MTLTexture:Metal 纹理对象MTLDevice:Metal 设备对象MTKTextureLoader.Origin:纹理坐标原点枚举MTKTextureLoader.CubeLayout:立方体贴图布局枚举MTKTextureLoader.Option:加载选项类型别名MTKTextureLoader.Callback:异步回调类型别名
Giscus 未启用:请在
src/site.config.ts中配置 repoId 与 categoryId。