寫在前面:
對Metal技術感興趣的同學,可以關注我的專題:Metal專輯
也可以關注我個人的簡書賬號:張芳濤
所有的代碼存儲的Github地址是:Metal
OSX平臺相關技術實現
在本系列的第一部分中,我們介紹了MetalKit
框架。 讓我們重新使用第1部分中的項目,并從我們離開的地方拿起。 如果再看一下render()
函數,它看起來像這樣:
func render() {
let device = MTLCreateSystemDefaultDevice()!
self.device = device
let rpd = MTLRenderPassDescriptor()
let bleen = MTLClearColor(red: 0, green: 0.5, blue: 0.5, alpha: 1)
rpd.colorAttachments[0].texture = currentDrawable!.texture
rpd.colorAttachments[0].clearColor = bleen
rpd.colorAttachments[0].loadAction = .Clear
let commandQueue = device.newCommandQueue()
let commandBuffer = commandQueue.commandBuffer()
let encoder = commandBuffer.renderCommandEncoderWithDescriptor(rpd)
encoder.endEncoding()
commandBuffer.presentDrawable(currentDrawable!)
commandBuffer.commit()
}
讓我們稍微改進一下這個代碼。 首先,由于我們的類子類MTKView
,它已經有了自己的設備,因此不需要聲明另一個設備。 這可以讓我們將前兩行減少到一個:
device = MTLCreateSystemDefaultDevice()
上一篇博客我們說過,我們應該確保currentDrawable
和currentRenderPassDescriptor
不是零,否則應用程序會崩潰。 為了簡單起見,我們在第一部分中沒有這樣做,現在就讓我們來做。 這也將幫助我們擺脫更多行代碼。 該函數的最終版本如下所示:
func render() {
device = MTLCreateSystemDefaultDevice()
if let rpd = currentRenderPassDescriptor, drawable = currentDrawable {
rpd.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 0.5, 1.0)
let command_buffer = device!.newCommandQueue().commandBuffer()
let command_encoder = command_buffer.renderCommandEncoderWithDescriptor(rpd)
command_encoder.endEncoding()
command_buffer.presentDrawable(drawable)
command_buffer.commit()
}
}
你可能想知道colorAttachments [0]
是什么意思。 為了設置rendering pipeline state
(渲染管道狀態),Metal
框架提供了3種我們可以寫入的附件類型:
colorAttachments
depthAttachmentPixelFormat
stencilAttachmentPixelFormat
我們只對現在存儲顏色數據感興趣,colorAttachments
是一組紋理,用于保存繪圖結果并將其顯示在屏幕上。 我們目前只有一個這樣的紋理 - 數組的第一個元素(在索引0
處)。 好的,現在是運行應用程序的好時機,并且確保您仍然看到上次看到的相同顏色的背景。 好多了! 只需9
行代碼,我們就可以在我們的GPU
上運行安全的Metal
代碼。
接下來,讓我們深入一個新的Metal
主題 - 在屏幕上繪制幾何圖形。 所有關于OpenGL
的圖形教程都以Hello,Triangle
類型的程序開始,因為三角形是可以在屏幕上繪制的幾何形狀的最簡單形式。 它是一個2D graphics
基本元素,圖形世界中的所有其他對象都由三角形組成,因此這是一個很好的開始。 想象一下屏幕坐標系統的軸線穿過屏幕的中心,坐標系(0,0)
。 屏幕邊緣的值分別為-1
和1
。 讓我們創建一個浮點數組和一個緩沖區來保存三角形的頂點值。 初始化device
后立即插入這些行:
let vertex_data:[Float] = [-1.0, -1.0, 0.0, 1.0,
1.0, -1.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0]
let data_size = vertex_data.count * sizeof(Float)
let vertex_buffer = device!.newBufferWithBytes(vertex_data, length: data_size, options: [])
上面的頂點按照左下角,右下角和頂部中心的順序排列。 我們注意到每個頂點使用4
個浮點數作為其坐標。 前兩個是x
和y
軸。 我們這次沒有使用的是:第三個是depth(Z軸)
,第四個是W coordinate
(W坐標),使得我們的坐標是homogeneous
(均勻)的。 我們將在下一篇博客中談論它們。 然后我們計算這個數組的大小,簡單地說就是12
個浮點數的大小,最后我們根據數組和它的大小創建緩沖區。 現在我們已經存儲了頂點,我們需要一種方法將它們發送到GPU
,以便它們可以顯示在屏幕上。 讓我們看看有助于在屏幕上繪制圖形的整個過程(pipeline
):
到目前為止,我們已經完成了第一階段,存儲頂點。 您注意到下一個階段需要我們有一個名為shader
(著色器)的新構造。 shader
(著色器)是允許程序員用自定義函數干涉圖形管線的地方。 Metal
提供了幾種著色器,但是,今天我們只看其中的兩種:負責點的location
(位置)的vertex shader
(頂點著色器)和負責點的color
(顏色)的fragment shader
(片段著色器)。
Metal
框架提供了一個函數,我們可以調用該函數來創建一個函數庫(著色器),所以我們來創建它:
let library = device!.newDefaultLibrary()!
let vertex_func = library.newFunctionWithName("vertex_func")
let frag_func = library.newFunctionWithName("fragment_func")
我們創建了兩個新的Functions
(函數),并將它們指向它們相應的著色器(我們將在后面創建它們)。 下一步是創建一個Render Pipeline Descriptor
(渲染管線描述符),它需要知道我們的著色器:
let rpld = MTLRenderPipelineDescriptor()
rpld.vertexFunction = vertex_func
rpld.fragmentFunction = frag_func
rpld.colorAttachments[0].pixelFormat = .BGRA8Unorm
您可能想知道.BGRA8Unorm
的含義。 此設置配置像素格式,以便通過渲染管線的所有內容都符合相同的顏色分量順序(在本例中為Blue
(藍色),Green
(綠色),Red
(紅色),Alpha
(阿爾法))以及尺寸(在這種情況下,8-bit
(8位)顏色值變為 從0
到255
)。 最后一步是根據上述descriptor
(描述符)創建Render Pipeline State
(渲染管線狀態):
let rps = try! device!.newRenderPipelineStateWithDescriptor(rpld)
最后,我們只需要讓命令encoder
(編碼器)知道我們的三角形,因此在創建encoder
(編碼器)后立即添加以下幾行:
command_encoder.setRenderPipelineState(rps)
command_encoder.setVertexBuffer(vertex_buffer, offset: 0, atIndex: 0)
command_encoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
現在讓我們回到我們在創建Library
(庫)時承諾創建的兩個shaders
(著色器)。 為此,我們需要在Xcode
中創建一個新文件。 選擇Metal File
類型,將其命名為Shaders.metal
或類似的東西,然后單擊Create
(創建)。 您將立即注意到代碼與Swift
不太相似,這是因為Metal shading language
(Metal著色語言)基于C ++
。 讓我們添加下面的代碼:
#include <metal_stdlib>
using namespace metal;
struct Vertex {
float4 position [[position]];
};
vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]], uint vid [[vertex_id]]) {
return vertices[vid];
}
fragment float4 fragment_func(Vertex vert [[stage_in]]) {
return float4(0.7, 1, 1, 1);
}
代碼非常簡單。 我們首先創建一個名為Vertex
的結構體,它只有一個成員 - 一個位置數組數組。 我們注意到數組是float4
,它在著色語言中與我們前面創建的頂點相同,每個頂點使用4
個浮點數。 我們在下一次留下[[...]]
語法的解釋。 然后我們有返回當前頂點location
(位置)的vertex_func
著色器和返回當前頂點顏色的fragment_func
著色器。 我們對特定的顏色值進行了硬編碼,但是我們可以將color
(顏色)結構成員添加到Vertex
(頂點)并為每個頂點分別設置顏色。
如果你運行該應用程序,你應該看到一個這樣的三角形:
代碼地址:Ch03-OSX
iOS平臺相關技術實現
Shader.metal相關代碼:
#include <metal_stdlib>
using namespace metal;
struct Vertex {
float4 position [[position]];
};
vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]],uint vid [[vertex_id]]){
return vertices[vid];
}
fragment float4 fragment_func(Vertex vert [[stage_in]]){
return float4(0.7,1,1,1);
}
MetalView.swift相關代碼實現:
import MetalKit
class MetalView: MTKView {
var commandQueue: MTLCommandQueue?
var rps: MTLRenderPipelineState?
var vertexData: [Float]?
var vertexBuffer: MTLBuffer?
required init(coder: NSCoder) {
super.init(coder: coder)
render()
}
func render(){
device = MTLCreateSystemDefaultDevice()
commandQueue = device?.makeCommandQueue()
vertexData = [-1.0,-1.0,0.0,1.0,
1.0,-1.0,0.0,1.0,
0.0,1.0,0.0,1.0]
let dataSize = vertexData!.count * MemoryLayout<Float>.size
vertexBuffer = device?.makeBuffer(bytes: vertexData!, length: dataSize, options: [])
let library = device?.makeDefaultLibrary()!
let vertex_func = library?.makeFunction(name: "vertex_func")
let frag_func = library?.makeFunction(name: "fragment_func")
let rpld = MTLRenderPipelineDescriptor()
rpld.vertexFunction = vertex_func
rpld.fragmentFunction = frag_func
rpld.colorAttachments[0].pixelFormat = .bgra8Unorm
do{
try rps = device?.makeRenderPipelineState(descriptor: rpld)
}catch let error{
fatalError("\(error)")
}
}
override func draw(_ rect: CGRect) {
if let drawable = currentDrawable, let rpd = currentRenderPassDescriptor {
rpd.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 0.5, 1.0)
let commandBuffer = commandQueue!.makeCommandBuffer()
let commandEncode = commandBuffer?.makeRenderCommandEncoder(descriptor: rpd)
commandEncode?.setRenderPipelineState(rps!)
commandEncode?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
commandEncode?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
commandEncode?.endEncoding()
commandBuffer?.present(drawable)
commandBuffer?.commit()
}
}
}
執行效果:
代碼地址: Ch03-iOS
TvOS平臺相關技術實現
Shader.metal相關代碼:
#include <metal_stdlib>
using namespace metal;
struct Vertex {
float4 position [[position]];
};
vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]],uint vid [[vertex_id]]){
return vertices[vid];
}
fragment float4 fragment_func(Vertex vert [[stage_in]]){
return float4(0.7,1,1,1);
}
MetalView.swift相關代碼實現:
import MetalKit
class MetalView: MTKView {
var commandQueue: MTLCommandQueue?
var rps: MTLRenderPipelineState?
var vertexData: [Float]?
var vertexBuffer: MTLBuffer?
required init(coder: NSCoder) {
super.init(coder: coder)
render()
}
func render(){
device = MTLCreateSystemDefaultDevice()
commandQueue = device?.makeCommandQueue()
vertexData = [-1.0,-1.0,0.0,1.0,
1.0,-1.0,0.0,1.0,
0.0,1.0,0.0,1.0]
let dataSize = vertexData!.count * MemoryLayout<Float>.size
vertexBuffer = device?.makeBuffer(bytes: vertexData!, length: dataSize, options: [])
let library = device?.makeDefaultLibrary()!
let vertex_func = library?.makeFunction(name: "vertex_func")
let frag_func = library?.makeFunction(name: "fragment_func")
let rpld = MTLRenderPipelineDescriptor()
rpld.vertexFunction = vertex_func
rpld.fragmentFunction = frag_func
rpld.colorAttachments[0].pixelFormat = .bgra8Unorm
do{
try rps = device?.makeRenderPipelineState(descriptor: rpld)
}catch let error{
fatalError("\(error)")
}
}
override func draw(_ rect: CGRect) {
if let drawable = currentDrawable, let rpd = currentRenderPassDescriptor {
rpd.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 0.5, 1.0)
let commandBuffer = commandQueue!.makeCommandBuffer()
let commandEncode = commandBuffer?.makeRenderCommandEncoder(descriptor: rpd)
commandEncode?.setRenderPipelineState(rps!)
commandEncode?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
commandEncode?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
commandEncode?.endEncoding()
commandBuffer?.present(drawable)
commandBuffer?.commit()
}
}
}
執行效果:
代碼地址: Ch03-TvOS