如何将 MetalKit 纹理加载器与金属堆一起使用?

How do I use MetalKit texture loader with Metal heaps?

我有一组金属纹理作为纹理集存储在 Xcode 资产目录中。我正在使用 MTKTextureLoader.newTexture(name:scaleFactor:bundle:options).

加载这些

然后我使用 MTLArgumentEncoder 将所有纹理编码到 Metal 2 参数缓冲区中。

效果很好。但是,Introducing Metal 2 WWDC 2017 session recommends combining argument buffers with resource heaps for even better performance, and I'm quite keen to try this. According to the Argument Buffer documentation,不必在参数缓冲区中的每个纹理上调用 MTLRenderCommandEncoder.useResource,您只需调用useHeap 在分配纹理的堆上。

但是,我还没有找到将 MTKTextureLoaderMTLHeap 一起使用的直接方法。它似乎没有从堆中分配纹理的加载选项。

我猜该方法是:

这似乎是一种相当 long-winded 的方法,我还没有看到任何这样的例子,所以我想我会先在这里问一下,以防我遗漏了一些明显的东西。

我应该放弃 MTKTextureLoader,并搜索一些 pre-MetalKit 从资产目录加载纹理的艺术吗?

我正在使用 Swift,但很高兴接受 Objective-C 个答案。

嗯,我上面概述的方法似乎有效。正如预测的那样,它很漂亮long-winded。我很想知道是否有人有更优雅的东西。

enum MetalError: Error {
    case anErrorOccured
}

extension MTLTexture {
    var descriptor: MTLTextureDescriptor {
        let descriptor = MTLTextureDescriptor()
        descriptor.width = width
        descriptor.height = height
        descriptor.depth = depth
        descriptor.textureType = textureType
        descriptor.cpuCacheMode = cpuCacheMode
        descriptor.storageMode = storageMode
        descriptor.pixelFormat = pixelFormat
        descriptor.arrayLength = arrayLength
        descriptor.mipmapLevelCount = mipmapLevelCount
        descriptor.sampleCount = sampleCount
        descriptor.usage = usage
        return descriptor
    }

    var size: MTLSize {
        return MTLSize(width: width, height: height, depth: depth)
    }
}

extension MTKTextureLoader {
    func newHeap(withTexturesNamed names: [String], queue: MTLCommandQueue, scaleFactor: CGFloat, bundle: Bundle?, options: [MTKTextureLoader.Option : Any]?, onCompletion: (([MTLTexture]) -> Void)?) throws -> MTLHeap {
        let device = queue.device
        let sourceTextures = try names.map { name in
            return try newTexture(name: name, scaleFactor: scaleFactor, bundle: bundle, options: options)
        }
        let storageMode: MTLStorageMode = .private
        let descriptors: [MTLTextureDescriptor] = sourceTextures.map { source in
            let desc = source.descriptor
            desc.storageMode = storageMode
            return desc
        }
        let sizeAligns = descriptors.map { device.heapTextureSizeAndAlign(descriptor: [=10=]) }
        let heapDescriptor = MTLHeapDescriptor()
        heapDescriptor.size = sizeAligns.reduce(0) { [=10=] + .size }
        heapDescriptor.cpuCacheMode = descriptors[0].cpuCacheMode
        heapDescriptor.storageMode = storageMode
        guard let heap = device.makeHeap(descriptor: heapDescriptor),
            let buffer = queue.makeCommandBuffer(),
            let blit = buffer.makeBlitCommandEncoder()
            else {
            throw MetalError.anErrorOccured
        }
        let destTextures = descriptors.map { descriptor in
            return heap.makeTexture(descriptor: descriptor)
        }
        let origin = MTLOrigin()
        zip(sourceTextures, destTextures).forEach {(source, dest) in
            blit.copy(from: source, sourceSlice: 0, sourceLevel: 0, sourceOrigin: origin, sourceSize: source.size, to: dest, destinationSlice: 0, destinationLevel: 0, destinationOrigin: origin)
            blit.generateMipmaps(for: dest)
        }
        blit.endEncoding()
        buffer.addCompletedHandler { _ in
            onCompletion?(destTextures)
        }
        buffer.commit()
        return heap
    }
}