ARKit系列文章目錄objective-c
SIMD全稱Single Instruction Multiple Data,單指令多數據流,可以複製多個操做數,讀做[洗目~底]swift
以加法指令爲例,單指令單數據(SISD)的CPU對加法指令譯碼後,執行部件先訪問內存,取得第一個操做數;以後再一次訪問內存,取得第二個操做數;隨後才能進行求和運算。而在SIMD型的CPU中,指令譯碼後幾個執行部件同時訪問內存,一次性得到全部操做數進行運算。這個特色使SIMD特別適合於多媒體應用等數據密集型運算。數組
iOS11開始,正式引入這個運算,用於加速圖形圖象的處理.安全
SceneKit
中保留舊屬性名稱和類型的同時,在iOS11添加新類型,並以simd_
前綴開頭好比SCNNode
類型的屬性中:架構
// 舊的變換矩陣
open var transform: SCNMatrix4
// 新的simd類型矩陣
@available(iOS 11.0, *)
open var simdTransform: simd_float4x4
複製代碼
在SceneKit
中這二者只要改變一個,另外一個也會隨之改變,做用是同樣的,只是格式不一樣.ide
ARKit
在iOS11才推出,直接使用了新類型,名稱前也沒有前綴,好比在ARAnchor
類型的屬性中:函數
/** The transformation matrix that defines the anchor’s rotation, translation and scale in world coordinates. */
open var transform: matrix_float4x4 { get }
複製代碼
下面咱們來仔細看看matrix_float4x4
這個結構體,只是個別名,真實類型仍是simd_float4x4
:post
public typealias matrix_float4x4 = simd_float4x4
複製代碼
/*! @abstract A matrix with 4 rows and 4 columns. */
public struct simd_float4x4 {
public var columns: (simd_float4, simd_float4, simd_float4, simd_float4)
public init()
public init(columns: (simd_float4, simd_float4, simd_float4, simd_float4))
}
複製代碼
裏面是4個simd_float4
向量類型組成的,而這只是個別名,真正類型是float4
,它也是結構體:測試
/*! @abstract A vector of four 32-bit floating-point numbers. * @description In C++ and Metal, this type is also available as * simd::float4. The alignment of this type is greater than the alignment * of float; if you need to operate on data buffers that may not be * suitably aligned, you should access them using simd_packed_float4 * instead. 該向量,由4個32-bit浮點數組成.在C++和Metal中,該類型爲simd::float4.該類型的對齊大於float類型的對齊;若是你須要直接操做數據緩衝可能會有對齊問題,你應該經過simd_packed_float4來訪問. */
public typealias simd_float4 = float4
/// A vector of four `Float`. This corresponds to the C and
/// Obj-C type `vector_float4` and the C++ type `simd::float4`.
/// 四個`Float`組成的向量.在C和ObjC中的對應類型爲`vector_float4`,在C++中爲`simd::float4`.
public struct float4 {
public var x: Float
public var y: Float
public var z: Float
public var w: Float
/// Initialize to the zero vector.
public init()
/// Initialize a vector with the specified elements.
public init(_ x: Float, _ y: Float, _ z: Float, _ w: Float)
/// Initialize a vector with the specified elements.
public init(x: Float, y: Float, z: Float, w: Float)
/// Initialize to a vector with all elements equal to `scalar`.
public init(_ scalar: Float)
/// Initialize to a vector with elements taken from `array`.
///
/// - Precondition: `array` must have exactly four elements.
public init(_ array: [Float])
/// Access individual elements of the vector via subscript.
public subscript(index: Int) -> Float
}
複製代碼
以float4
爲例,總共有多種類型和它相關:ui
public typealias simd_float4 = float4
public typealias vector_float4 = simd_float4
public typealias simd_packed_float4 = float4
還有一個是已經廢除的packed_float4
:
public typealias packed_float4 = simd_packed_float4
Xcode文檔對此進行了解釋:
這些類型是基於clang特性,叫作"擴展向量類型"或"OpenCL向量類型"(可用在C,Objective-C和C++中).還有一些新特性讓它比傳統的simd類型更方便使用:
__builtin_shufflevector
和 __builtin_convertvector
.simd向量的類型定義有:
simd_charN where N is 1, 2, 3, 4, 8, 16, 32, or 64.
simd_ucharN where N is 1, 2, 3, 4, 8, 16, 32, or 64.
simd_shortN where N is 1, 2, 3, 4, 8, 16, or 32.
simd_ushortN where N is 1, 2, 3, 4, 8, 16, or 32.
simd_intN where N is 1, 2, 3, 4, 8, or 16.
simd_uintN where N is 1, 2, 3, 4, 8, or 16.
simd_floatN where N is 1, 2, 3, 4, 8, or 16.
simd_longN where N is 1, 2, 3, 4, or 8.
simd_ulongN where N is 1, 2, 3, 4, or 8.
simd_doubleN where N is 1, 2, 3, 4, or 8.
複製代碼
這些類型相比底層的標量類型有更大的對齊間隔;它們對齊的是:向量[1]的尺寸與目標平臺Clang中"最大對齊間隔"[2],這二者中的最小值.
[1]注意,3維向量和4維向量尺寸是同樣的,由於3維有一個隱藏的通道.
[2]通常來講,架構層面的向量寬度是16, 32, or 64 bytes.若是你須要精確控制對齊方式,須要當心一點,由於這個值會根據編譯設置的不一樣而變化.
對於simd_typeN
類型來講,除了N等於1或3外,都有相應類型的simd_packed_typeN
,它只要求對齊方式匹配底層標量類型.若是你須要處理指向標量的指針或包含標量的數組,就使用simd_packed_typeN
類型:
//float *pointerToFourFloats是指向四維Float數組的指針
void myFunction(float *pointerToFourFloats) {
// 這樣作是個bug,由於`pointerToFourFloats`並不知足`simd_float4`類型要求的對齊方式;強制轉換極可能在運行時致使崩潰
simd_float4 *vecptr = (simd_float4 *)pointerToFourFloats;
// 應該轉換爲`simd_packed_float4`類型:
simd_packed_float4 *vecptr = (simd_packed_float4 *)pointerToFourFloats;
// `simd_packed_float4`類型的對齊方式和`float`相同,因此這個轉換是安全的,可讓咱們成功加載一個向量.
// `simd_packed_float4`類型能夠不用轉換就直接賦值給`simd_float4`;它們的類型只有在做爲指針或數組時纔會有所不一樣.
simd_float4 vector = vecptr[0];
}
複製代碼
全部simd_開頭的類型,在C++中都在simd::命名空間中;好比,simd_char4
能夠用simd::char4
. 這些類型大部分都和Metal shader language中的向量類型相匹配,除了那些超過4維的向量,由於Metal中沒有超過4的向量.
在swift中沒有指針的轉換問題,而simd_floatN & vector_floatN & simd_packed_floatN
這些類型本質上都是floatN
類型,因此轉換就至關簡單了,直接init()
從新構造一個就能夠了:
let floats:[Float] = [1,2,3,4]
// 直接轉換爲simd_float4類型,本質是調用了float4.init()方法,該init()方法能夠接收多種類型,其中就包括了[Float]類型參數
let testVect4:simd_float4 = simd_float4(floats)
// 轉換爲simd_packed_float4類型,再賦值給simd_float4類型,本質也是調用了float4.init()方法
let result2Vect4:simd_float4 = simd_packed_float4(floats)
// 先轉爲simd_packed_float4類型,原理也是init()方法,不使用as再次轉換也能夠
let packedVect4:simd_packed_float4 = simd_packed_float4(floats)
let resultVect4:simd_float4 = packedVect4 as simd_float4
// 直接強制轉換是不能夠的,好比下面的方法
let result1111Vect4:simd_float4 = (simd_float4)floats
let result2222Vect4:simd_float4 = floats as! simd_float4
複製代碼
反過來轉換也是同樣的道理,調用init()方法
let simdVect4 = simd_float4(1,2,3,4)
// 調用數組的Array.init()方法
let array:[Float] = Array(simdVect4);
複製代碼
在測試中發現,swift中的[Float]
類型和simd_float4
類型即float4
在內存中儲存結構徹底不一樣:
OC中由於有指針的概念,因此須要注意的是指針的類型.
在OC和C中,直接轉換指針類型,數據的結構並無變,而結構體類型在通過一次賦值後,會發生複製,在複製過程當中類型轉換就完成了:
float floats[4] = {1,2,3,4};
// 此時packedVect4指針仍然指向的是數組開頭,只是指針的類型不一樣
simd_packed_float4 *packedVect4 = (simd_packed_float4 *)floats;
// *packedVect4表示取出packedVect4指針指向的數據,而後賦值給resultVect4,C語言中常量和結構體的賦值是值傳遞,所謂值傳遞就是複製一份出來,在複製並賦值的同時,類型轉換就完成了.
simd_float4 resultVect4 = *packedVect4;
// 由於simd_packed_float4類型和simd_float4本質都是float4類型,理論上用哪一個都無所謂,我測試的過程當中未發現異常.
// 可是蘋果給出了示例代碼中使用了simd_packed_float4類型作中轉,並且特別說明:它們的類型只有在做爲指針或數組時纔會有所不一樣.因此在OC中仍是用simd_packed_float4中轉一下更安全.
simd_float4 result2Vect4 = *(simd_packed_float4 *)floats;
simd_float4 result3Vect4 = *(simd_float4*)floats;
複製代碼
相反的轉換,由於C語言中的數組初始化只能用{},因此限制了類型轉換,很是不便:
simd_float4 simdVect4 = simd_make_float4(1, 2, 3, 4);
// 只能一個個數值取出,再初始化C數組
float arr[4] = {simdVect4.x,simdVect4.y,simdVect4.z,simdVect4.w};
float arr2[4] = {simdVect4[0],simdVect4[1],simdVect4[2],simdVect4[3]};
複製代碼
測試中發現,不考慮內存對齊,OC中的float [4]
類型數組與simd_float4
類型在內存中儲存結構是同樣的:
在WWDC2018中,也講到了simd類型的應用. 主要有幾點:
舉例,之前的作法,常規數組很慢,simd很快:
// 求兩個向量的平均值,之前作法
var x:[Float] = [1,2,3,4]
var y:[Float] = [3,3,3,3]
var z = [Float](repeating:0, count:4)
for i in 0..<4 {
z[i] = (x[i] + y[i]) / 2.0
}
// 如今作法
let x = simd_float4(1,2,3,4)
let y = simd_float4(3,3,3,3)
let z = 0.5 * (x + y)
複製代碼
重點講一下四元數在旋轉中的使用.
// 要旋轉的向量(下圖中紅點)
let original = simd_float3(0,0,1)
// 旋轉軸和角度
let quaternion = simd_quatf(angle: .pi / -3,
axis: simd_float3(1,0,0))
// 應用旋轉(下圖中黃點)
let rotatedVector = simd_act(quaternion, original)
複製代碼
開發中,咱們不會只繞一個軸旋轉,可能會是兩個或或複雜.
// 要旋轉的向量(紅點)
let original = simd_float3(0,0,1)
// 旋轉軸
let quaternion = simd_quatf(angle: .pi / -3,
axis: simd_float3(1,0,0))
let quaternion2 = simd_quatf(angle: .pi / 3,
axis: simd_float3(0,1,0))
// 組合兩個旋轉
let quaternion3 = quaternion2 * quaternion
// 應用旋轉(黃點)
let rotatedVector = simd_act(quaternion3, original)
複製代碼
另外simd還支持四元數的插值運算,Slerp Interpolation(球面線性插值)和Spline Interpolation(樣條曲線插值).
// Slerp Interpolation球面線性插值
let blue = simd_quatf(...) //藍色
let green = simd_quatf(...) //綠色
let red = simd_quatf(...) //紅包
for t: Float in stride(from: 0, to: 1, by: 0.001) {
let q = simd_slerp(blue, green, t) //從藍色到綠色的插值曲線(最短球面距離)
// Code to Add Line Segment at `q.act(original)`
}
for t: Float in stride(from: 0, to: 1, by: 0.001) {
let q = simd_slerp_longest(green, red, t) //從藍色到綠色的插值曲線(最長球面距離)
// Code to Add Line Segment at `q.act(original)`
}
複製代碼
// Spline Interpolation樣條曲線插值
let original = simd_float3(0,0,1)
let rotations: [simd_quatf] = ...
for i in 1 ... rotations.count - 3 {
for t: Float in stride(from: 0, to: 1, by: 0.001) {
let q = simd_spline(rotations[i - 1],
rotations[i],
rotations[i + 1],
rotations[i + 2],
t)
}
// Code to Add Line Segment at `q.act(original)`
}
複製代碼
二者在旋轉運動中的區別仍是很明顯的,以下圖頂點軌跡