在 iOS 设备上 Swift 3 中设置 Metal
Setting up Metal in Swift 3 on an iOS device
我一直在尝试将 Apple 的 MetalBasicTessellation 项目转换为在 iPad Air 3 上的 swift 3 中工作,但迄今为止没有成功。令人沮丧的是,该项目带有一个 iOS 实现(用 objectiveC 和一个 swift 游乐场编写),但没有 swift 3 实现。
我已经得到要编译的代码,但是在我的 iPad 上 运行 失败,出现以下错误:
2017-05-14 14:25:54.268400-0700 MetalBasicTessellation[2436:570250] -[MTLRenderPipelineDescriptorInternal validateWithDevice:], line 1728: error 'tessellation is only supported on MTLFeatureSet_iOS_GPUFamily3_v1 and later'
我很确定 iPad Air 2 是合规的,但我感觉这个错误是由于 MetalKitView 配置不当造成的。我已经对项目的 objective-c 和 playground 文件进行了逆向工程,但我已经尽了我目前的专业知识所能理解的范围。
//
// ViewController.swift
// MetalBasicTessellation
//
// Created by vladimir sierra on 5/10/17.
//
//
import UIKit
import Metal
import MetalKit
class ViewController: UIViewController {
@IBOutlet weak var mtkView: MTKView!
// Seven steps required to set up metal for rendering:
// 1. Create a MTLDevice
// 2. Create a CAMetalLayer
// 3. Create a Vertex Buffer
// 4. Create a Vertex Shader
// 5. Create a Fragment Shader
// 6. Create a Render Pipeline
// 7. Create a Command Queue
var device: MTLDevice! // to be initialized in viewDidLoad
//var metalLayer: CAMetalLayer! // to be initialized in viewDidLoad
var vertexBuffer: MTLBuffer! // to be initialized in viewDidLoad
var library: MTLLibrary!
// once we create a vertex and fragment shader, we combine them in an object called render pipeline. In Metal the shaders are precompiled, and the render pipeline configuration is compiled after you first set it up. This makes everything extremely efficient
var renderPipeline: MTLRenderPipelineState! // to be initialized in viewDidLoad
var commandQueue: MTLCommandQueue! // to be initialized in viewDidLoad
//var timer: CADisplayLink! // function to be called every time the device screen refreshes so we can redraw the screen
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
/*
if let window = view.window {
let scale = window.screen.nativeScale // (2 for iPhone 5s, 6 and iPads; 3 for iPhone 6 Plus)
let layerSize = view.bounds.size
// apply the scale to increase the drawable texture size.
view.contentScaleFactor = scale
//metalLayer.frame = CGRect(x: 0, y: 0, width: layerSize.width, height: layerSize.height)
//metalLayer.drawableSize = CGSize(width: layerSize.width * scale, height: layerSize.height * scale)
} */
}
override func viewDidLoad() {
super.viewDidLoad()
device = MTLCreateSystemDefaultDevice() // returns a reference to the default MTLDevice
//device.supportsFeatureSet(MTLFeatureSet_iOS_GPUFamily3_v2)
// set up layer to display metal content
//metalLayer = CAMetalLayer() // initialize metalLayer
//metalLayer.device = device // device the layer should use
//metalLayer.pixelFormat = .bgra8Unorm // normalized 8 bit rgba
//metalLayer.framebufferOnly = true // set to true for performance issues
//view.layer.addSublayer(metalLayer) // add sublayer to main view's layer
// precompile custom metal functions
let defaultLibrary = device.newDefaultLibrary()! // MTLLibrary object with precompiled shaders
let fragmentProgram = defaultLibrary.makeFunction(name: "tessellation_fragment")
let vertexProgram = defaultLibrary.makeFunction(name: "tessellation_vertex_triangle")
// Setup Compute Pipeline
let kernelFunction = defaultLibrary.makeFunction(name: "tessellation_kernel_triangle")
var computePipeline: MTLComputePipelineState?
do {
computePipeline = try device.makeComputePipelineState(function: kernelFunction!)
} catch let error as NSError {
print("compute pipeline error: " + error.description)
}
// Setup Vertex Descriptor
let vertexDescriptor = MTLVertexDescriptor()
vertexDescriptor.attributes[0].format = .float4
vertexDescriptor.attributes[0].offset = 0
vertexDescriptor.attributes[0].bufferIndex = 0;
vertexDescriptor.layouts[0].stepFunction = .perPatchControlPoint
vertexDescriptor.layouts[0].stepRate = 1
vertexDescriptor.layouts[0].stride = 4*MemoryLayout<Float>.size
// Setup Render Pipeline
let renderPipelineDescriptor = MTLRenderPipelineDescriptor()
renderPipelineDescriptor.vertexDescriptor = vertexDescriptor
//renderPipelineDescriptor.fragmentFunction = defaultLibrary.makeFunction(name: "tessellation_fragment")
renderPipelineDescriptor.fragmentFunction = fragmentProgram
//renderPipelineDescriptor.vertexFunction = defaultLibrary.makeFunction(name: "tessellation_vertex_triangle")
renderPipelineDescriptor.vertexFunction = vertexProgram
//renderPipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm // normalized 8 bit rgba
renderPipelineDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat
renderPipelineDescriptor.isTessellationFactorScaleEnabled = false
renderPipelineDescriptor.tessellationFactorFormat = .half
renderPipelineDescriptor.tessellationControlPointIndexType = .none
renderPipelineDescriptor.tessellationFactorStepFunction = .constant
renderPipelineDescriptor.tessellationOutputWindingOrder = .clockwise
renderPipelineDescriptor.tessellationPartitionMode = .fractionalEven
renderPipelineDescriptor.maxTessellationFactor = 64;
// Compile renderPipeline
do {
renderPipeline = try device.makeRenderPipelineState(descriptor: renderPipelineDescriptor)
} catch let error as NSError {
print("render pipeline error: " + error.description)
}
// Setup Buffers
let tessellationFactorsBuffer = device.makeBuffer(length: 256, options: MTLResourceOptions.storageModePrivate)
let controlPointPositions: [Float] = [
-0.8, -0.8, 0.0, 1.0, // lower-left
0.0, 0.8, 0.0, 1.0, // upper-middle
0.8, -0.8, 0.0, 1.0, // lower-right
]
let controlPointsBuffer = device.makeBuffer(bytes: controlPointPositions, length:256 , options: [])
// Tessellation Pass
let commandBuffer = commandQueue.makeCommandBuffer()
let computeCommandEncoder = commandBuffer.makeComputeCommandEncoder()
computeCommandEncoder.setComputePipelineState(computePipeline!)
let edgeFactor: [Float] = [16.0]
let insideFactor: [Float] = [8.0]
computeCommandEncoder.setBytes(edgeFactor, length: MemoryLayout<Float>.size, at: 0)
computeCommandEncoder.setBytes(insideFactor, length: MemoryLayout<Float>.size, at: 1)
computeCommandEncoder.setBuffer(tessellationFactorsBuffer, offset: 0, at: 2)
computeCommandEncoder.dispatchThreadgroups(MTLSizeMake(1, 1, 1), threadsPerThreadgroup: MTLSizeMake(1, 1, 1))
computeCommandEncoder.endEncoding()
let renderPassDescriptor = mtkView.currentRenderPassDescriptor
let renderCommandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor!)
renderCommandEncoder.setRenderPipelineState(renderPipeline!)
renderCommandEncoder.setVertexBuffer(controlPointsBuffer, offset: 0, at: 0)
renderCommandEncoder.setTriangleFillMode(.lines)
renderCommandEncoder.setTessellationFactorBuffer(tessellationFactorsBuffer, offset: 0, instanceStride: 0)
renderCommandEncoder.drawPatches(numberOfPatchControlPoints: 3, patchStart: 0, patchCount: 1, patchIndexBuffer: nil, patchIndexBufferOffset: 0, instanceCount: 1, baseInstance: 0)
renderCommandEncoder.endEncoding()
commandBuffer.present(mtkView.currentDrawable!)
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
/*
// finally create an ordered list of commands forthe GPU to execute
commandQueue = device.makeCommandQueue()
timer = CADisplayLink(target: self, selector: #selector(ViewController.gameloop)) // call gameloop every time the screen refreshes
timer.add(to: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)
*/
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
func render() {
guard let drawable = metalLayer?.nextDrawable() else { return } // returns the texture to draw into in order for something to appear on the screen
//objectToDraw.render(commandQueue: commandQueue, renderPipeline: renderPipeline, drawable: drawable, clearColor: nil)
}
// this is the routine that gets run every time the screen refreshes
func gameloop() {
autoreleasepool {
self.render()
}
} */
}
整个git都可以找到here
某种金属大师灵魂会伸出援手吗?那里的文档非常稀少。
docs for MTLFeatureSet_iOS_GPUFamily3_v1
说:
Introduced with the Apple A9 GPU and iOS 9.0.
(强调已添加。)
同时,iOS Device Compatibility Reference: Hardware GPU Information 文章说 iPad Air 2 有一个 A8 GPU。
我不相信你的设备有能力。
一般来说,MTKView
的配置不会影响所支持的功能集。这是设备固有的(硬件和 OS 版本的组合)。您可以使用 supportsFeatureSet(_:)
method of MTLDevice
查询设备是否支持给定的功能集。由于可以(并且通常)独立于任何其他对象(例如 MTKView
)获取设备,因此功能集支持不能依赖于此类其他对象。
我一直在尝试将 Apple 的 MetalBasicTessellation 项目转换为在 iPad Air 3 上的 swift 3 中工作,但迄今为止没有成功。令人沮丧的是,该项目带有一个 iOS 实现(用 objectiveC 和一个 swift 游乐场编写),但没有 swift 3 实现。
我已经得到要编译的代码,但是在我的 iPad 上 运行 失败,出现以下错误:
2017-05-14 14:25:54.268400-0700 MetalBasicTessellation[2436:570250] -[MTLRenderPipelineDescriptorInternal validateWithDevice:], line 1728: error 'tessellation is only supported on MTLFeatureSet_iOS_GPUFamily3_v1 and later'
我很确定 iPad Air 2 是合规的,但我感觉这个错误是由于 MetalKitView 配置不当造成的。我已经对项目的 objective-c 和 playground 文件进行了逆向工程,但我已经尽了我目前的专业知识所能理解的范围。
//
// ViewController.swift
// MetalBasicTessellation
//
// Created by vladimir sierra on 5/10/17.
//
//
import UIKit
import Metal
import MetalKit
class ViewController: UIViewController {
@IBOutlet weak var mtkView: MTKView!
// Seven steps required to set up metal for rendering:
// 1. Create a MTLDevice
// 2. Create a CAMetalLayer
// 3. Create a Vertex Buffer
// 4. Create a Vertex Shader
// 5. Create a Fragment Shader
// 6. Create a Render Pipeline
// 7. Create a Command Queue
var device: MTLDevice! // to be initialized in viewDidLoad
//var metalLayer: CAMetalLayer! // to be initialized in viewDidLoad
var vertexBuffer: MTLBuffer! // to be initialized in viewDidLoad
var library: MTLLibrary!
// once we create a vertex and fragment shader, we combine them in an object called render pipeline. In Metal the shaders are precompiled, and the render pipeline configuration is compiled after you first set it up. This makes everything extremely efficient
var renderPipeline: MTLRenderPipelineState! // to be initialized in viewDidLoad
var commandQueue: MTLCommandQueue! // to be initialized in viewDidLoad
//var timer: CADisplayLink! // function to be called every time the device screen refreshes so we can redraw the screen
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
/*
if let window = view.window {
let scale = window.screen.nativeScale // (2 for iPhone 5s, 6 and iPads; 3 for iPhone 6 Plus)
let layerSize = view.bounds.size
// apply the scale to increase the drawable texture size.
view.contentScaleFactor = scale
//metalLayer.frame = CGRect(x: 0, y: 0, width: layerSize.width, height: layerSize.height)
//metalLayer.drawableSize = CGSize(width: layerSize.width * scale, height: layerSize.height * scale)
} */
}
override func viewDidLoad() {
super.viewDidLoad()
device = MTLCreateSystemDefaultDevice() // returns a reference to the default MTLDevice
//device.supportsFeatureSet(MTLFeatureSet_iOS_GPUFamily3_v2)
// set up layer to display metal content
//metalLayer = CAMetalLayer() // initialize metalLayer
//metalLayer.device = device // device the layer should use
//metalLayer.pixelFormat = .bgra8Unorm // normalized 8 bit rgba
//metalLayer.framebufferOnly = true // set to true for performance issues
//view.layer.addSublayer(metalLayer) // add sublayer to main view's layer
// precompile custom metal functions
let defaultLibrary = device.newDefaultLibrary()! // MTLLibrary object with precompiled shaders
let fragmentProgram = defaultLibrary.makeFunction(name: "tessellation_fragment")
let vertexProgram = defaultLibrary.makeFunction(name: "tessellation_vertex_triangle")
// Setup Compute Pipeline
let kernelFunction = defaultLibrary.makeFunction(name: "tessellation_kernel_triangle")
var computePipeline: MTLComputePipelineState?
do {
computePipeline = try device.makeComputePipelineState(function: kernelFunction!)
} catch let error as NSError {
print("compute pipeline error: " + error.description)
}
// Setup Vertex Descriptor
let vertexDescriptor = MTLVertexDescriptor()
vertexDescriptor.attributes[0].format = .float4
vertexDescriptor.attributes[0].offset = 0
vertexDescriptor.attributes[0].bufferIndex = 0;
vertexDescriptor.layouts[0].stepFunction = .perPatchControlPoint
vertexDescriptor.layouts[0].stepRate = 1
vertexDescriptor.layouts[0].stride = 4*MemoryLayout<Float>.size
// Setup Render Pipeline
let renderPipelineDescriptor = MTLRenderPipelineDescriptor()
renderPipelineDescriptor.vertexDescriptor = vertexDescriptor
//renderPipelineDescriptor.fragmentFunction = defaultLibrary.makeFunction(name: "tessellation_fragment")
renderPipelineDescriptor.fragmentFunction = fragmentProgram
//renderPipelineDescriptor.vertexFunction = defaultLibrary.makeFunction(name: "tessellation_vertex_triangle")
renderPipelineDescriptor.vertexFunction = vertexProgram
//renderPipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm // normalized 8 bit rgba
renderPipelineDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat
renderPipelineDescriptor.isTessellationFactorScaleEnabled = false
renderPipelineDescriptor.tessellationFactorFormat = .half
renderPipelineDescriptor.tessellationControlPointIndexType = .none
renderPipelineDescriptor.tessellationFactorStepFunction = .constant
renderPipelineDescriptor.tessellationOutputWindingOrder = .clockwise
renderPipelineDescriptor.tessellationPartitionMode = .fractionalEven
renderPipelineDescriptor.maxTessellationFactor = 64;
// Compile renderPipeline
do {
renderPipeline = try device.makeRenderPipelineState(descriptor: renderPipelineDescriptor)
} catch let error as NSError {
print("render pipeline error: " + error.description)
}
// Setup Buffers
let tessellationFactorsBuffer = device.makeBuffer(length: 256, options: MTLResourceOptions.storageModePrivate)
let controlPointPositions: [Float] = [
-0.8, -0.8, 0.0, 1.0, // lower-left
0.0, 0.8, 0.0, 1.0, // upper-middle
0.8, -0.8, 0.0, 1.0, // lower-right
]
let controlPointsBuffer = device.makeBuffer(bytes: controlPointPositions, length:256 , options: [])
// Tessellation Pass
let commandBuffer = commandQueue.makeCommandBuffer()
let computeCommandEncoder = commandBuffer.makeComputeCommandEncoder()
computeCommandEncoder.setComputePipelineState(computePipeline!)
let edgeFactor: [Float] = [16.0]
let insideFactor: [Float] = [8.0]
computeCommandEncoder.setBytes(edgeFactor, length: MemoryLayout<Float>.size, at: 0)
computeCommandEncoder.setBytes(insideFactor, length: MemoryLayout<Float>.size, at: 1)
computeCommandEncoder.setBuffer(tessellationFactorsBuffer, offset: 0, at: 2)
computeCommandEncoder.dispatchThreadgroups(MTLSizeMake(1, 1, 1), threadsPerThreadgroup: MTLSizeMake(1, 1, 1))
computeCommandEncoder.endEncoding()
let renderPassDescriptor = mtkView.currentRenderPassDescriptor
let renderCommandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor!)
renderCommandEncoder.setRenderPipelineState(renderPipeline!)
renderCommandEncoder.setVertexBuffer(controlPointsBuffer, offset: 0, at: 0)
renderCommandEncoder.setTriangleFillMode(.lines)
renderCommandEncoder.setTessellationFactorBuffer(tessellationFactorsBuffer, offset: 0, instanceStride: 0)
renderCommandEncoder.drawPatches(numberOfPatchControlPoints: 3, patchStart: 0, patchCount: 1, patchIndexBuffer: nil, patchIndexBufferOffset: 0, instanceCount: 1, baseInstance: 0)
renderCommandEncoder.endEncoding()
commandBuffer.present(mtkView.currentDrawable!)
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
/*
// finally create an ordered list of commands forthe GPU to execute
commandQueue = device.makeCommandQueue()
timer = CADisplayLink(target: self, selector: #selector(ViewController.gameloop)) // call gameloop every time the screen refreshes
timer.add(to: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)
*/
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
func render() {
guard let drawable = metalLayer?.nextDrawable() else { return } // returns the texture to draw into in order for something to appear on the screen
//objectToDraw.render(commandQueue: commandQueue, renderPipeline: renderPipeline, drawable: drawable, clearColor: nil)
}
// this is the routine that gets run every time the screen refreshes
func gameloop() {
autoreleasepool {
self.render()
}
} */
}
整个git都可以找到here
某种金属大师灵魂会伸出援手吗?那里的文档非常稀少。
docs for MTLFeatureSet_iOS_GPUFamily3_v1
说:
Introduced with the Apple A9 GPU and iOS 9.0.
(强调已添加。)
同时,iOS Device Compatibility Reference: Hardware GPU Information 文章说 iPad Air 2 有一个 A8 GPU。
我不相信你的设备有能力。
一般来说,MTKView
的配置不会影响所支持的功能集。这是设备固有的(硬件和 OS 版本的组合)。您可以使用 supportsFeatureSet(_:)
method of MTLDevice
查询设备是否支持给定的功能集。由于可以(并且通常)独立于任何其他对象(例如 MTKView
)获取设备,因此功能集支持不能依赖于此类其他对象。