Dav*_*d H 4 opengl ios metal metalkit
几年前,Apple 开始警告任何在其应用程序中使用GLKit 的人OpenGL即将消失:
警告:OpenGLES 已弃用。考虑迁移到 Metal,而不是
警告:GLKit 已弃用。考虑迁移到 MetalKit
我的应用程序使用复杂的 OpenGL 类,并且我不知道OpenGL或Metal。Apple 有一些关于此的 WWDC 会议,但它们是针对OpenGL专家的。由于 Apple 有一天会删除 OpenGL,所以我想在我只有几个月的时间之前立即开始这项工作。我应该怎么办?
当我开始在 iOS12 中看到构建错误消息时:
\n\n\n警告:OpenGLES 已弃用。考虑迁移到 Metal
\n
\n警告:GLKit 已弃用。考虑迁移到 MetalKit
我知道我必须做点什么。谁知道 Apple 何时会删除OpenGL和GLKit?相信我,您不想等到几个月的时间才转换为Metal,因为这个过程绝不是直截了当的。
\n以下是我将Objective-C/OpenGL视图转换为Metal的过程。这是一个漫长而艰辛的过程,有好几次\n我把头伏在桌子上沮丧地哭泣。
\n我建议其他人也采取我采取的基本步骤:
\n就我而言,我需要克服三个障碍:将视图转换为Swift ,在Metal中重新创建功能,然后将所有GLK向量和矩阵值以及操作替换为simd。
\n我对继续进行的建议:
\n我无法强调这一点\xe2\x80\x94 每次更改一些操作并测试它们时都会提交!您肯定会破坏一些东西,这样您就可以引用早期的工作代码。
\n测试工具将被证明是有用的,因为您可能会发现时间变化会导致不良行为。就我而言,我创建了两个线束,第二个线束中有更多应用程序代码,以便我可以更好地调试实际使用情况。
\n我分叉了一个开源项目Panorama。master分支包含Metal/simd代码,Swift-OpenGL分支包含原始ObjectiveC代码以及Swift转换。这可以让读者并排比较两者。但是,您不需要参考它来了解如何将ObjectiveC中的OpenGL代码转换为Swift ,或将GLKit向量和矩阵转换为simd,如下所示。
\nOpenGL代码大量使用指针,而这些在Swift中有点麻烦。例如:
\nGLfloat *m_TexCoordsData; // treated as an array of pairs of floats\nglTexCoordPointer(2, GL_FLOAT, 0, m_TexCoordsData);\nRun Code Online (Sandbox Code Playgroud)\n成为
\nstruct Pointer2 {\n private var array: [SIMD2<Float>]\n\n init(size: Int) {\n let n: SIMD2<Float> = [Float.nan, Float.nan]\n array = Array<SIMD2<Float>>(repeating: n, count: size)\n }\n\n subscript(index: Int) -> SIMD2<Float>{\n get { return array[index] }\n set(newValue) { array[index] = newValue }\n }\n\n mutating func usingRawPointer(block: WithRawPtr) {\n array.withUnsafeBytes { (bufPtr) -> Void in\n block(bufPtr.baseAddress!)\n }\n }\n}\n private var tPtr: Pointer2 // m_TexCoordsData\n tPtr.usingRawPointer(block: { (ptr) in\n glTexCoordPointer(2, UInt32(GL_FLOAT), 0, ptr)\n })\nRun Code Online (Sandbox Code Playgroud)\n这一切都在最终的Metal代码中消失了。\n进行转换也发现了潜在的错误!
\n此外,我必须将许多值转换(转换)为更严格的 Swift 类型:
\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);\nRun Code Online (Sandbox Code Playgroud)\n成为
\nglTexParameteri(UInt32(GL_TEXTURE_2D), UInt32(GL_TEXTURE_WRAP_S), GLint(GL_REPEAT))\nRun Code Online (Sandbox Code Playgroud)\n这个过程非常乏味。然而,恕我直言, Swift代码更精简,更容易阅读。
\n几个函数很容易翻译:
\nGLKQuaternion GLKQuaternionFromTwoVectors(GLKVector3 u, GLKVector3 v) {\n GLKVector3 w = GLKVector3CrossProduct(u, v);\n GLKQuaternion q = GLKQuaternionMake(w.x, w.y, w.z, GLKVector3DotProduct(u, v));\n q.w += GLKQuaternionLength(q);\n return GLKQuaternionNormalize(q);\n}\nRun Code Online (Sandbox Code Playgroud)\n成为
\nfunc GLKQuaternionFromTwoVectors(_ u: GLKVector3, _ v: GLKVector3) -> GLKQuaternion {\n let w = GLKVector3CrossProduct(u, v)\n var q = GLKQuaternionMake(w.x, w.y, w.z, GLKVector3DotProduct(u, v))\n q.w += GLKQuaternionLength(q)\n return GLKQuaternionNormalize(q)\n}\nRun Code Online (Sandbox Code Playgroud)\n稍后你会发现翻译成simd并不那么容易。
\n不幸的是,这里没有魔杖。苹果公司在 WWDC 上有过一些关于这个问题的会议,但他们并没有真正启发我。Metal使用两种类型的内核:计算和着色器,其中计算更容易。然而,就我而言,我必须使用着色器,这对我来说更难掌握。
\n如果您对Metal一无所知,可以从 Ray Wenderlich 网站上的Metal 教程开始。第二篇文章更有帮助:Ray Wenderlich 网站上的Moving From OpenGL to Metal 。两者都大量引用了更多金属材料。
\n我发现另外两篇很有帮助的优秀文章:Donald Pinckney 的博客(旧版)。另一位有用的作者:Alex Barbulescu
\n沃伦·摩尔(Warren Moore)确实写了这本关于金属的书。他的书和文章非常宝贵!
\nOpenGL使用-1 到 1(z值)的剪辑空间。您需要在着色器中考虑到这一点。Warren Moore 个人建议我使用以下代码确保我的着色器不会返回负z值:
\nv.z = v.z * 0.5 + v.w * 0.5;\nRun Code Online (Sandbox Code Playgroud)\n这消除了完全重做可能使用负z的 OpenGL 代码的需要值的 OpenGL 代码。
\nMTLView的backgroundColor不是通过使用该属性来设置的,而是通过设置clearColor来设置的。
\n从应用程序空间到着色器空间的通信是使用结构完成的,必须分别在每个结构中单独定义。例如,在我的应用程序中这个结构:
\nprivate struct Uniforms {\n let projectionMatrix: simd_float4x4\n let attitudeMatrix: simd_float4x4\n}\nRun Code Online (Sandbox Code Playgroud)\n在着色器中定义为:
\nstruct Uniforms {\n float4x4 projectionMatrix;\n float4x4 attitudeMatrix;\n};\nRun Code Online (Sandbox Code Playgroud)\n这些结构是应用程序定义的。
\n如果您使用图像来创建纹理,则Metal中的情况有点不同。这
\nNSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], GLKTextureLoaderOriginBottomLeft, nil];\nGLKTextureInfo *info=[GLKTextureLoader textureWithContentsOfFile:path options:options error:&error];\nglBindTexture(GL_TEXTURE_2D, info.name);\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, IMAGE_SCALING);\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, IMAGE_SCALING);\nRun Code Online (Sandbox Code Playgroud)\n成为
\nlet loader: MTKTextureLoader = MTKTextureLoader(device: mtlDevice)\ndo {\n let texture = try loader.newTexture(cgImage: cgImage, options: [\n MTKTextureLoader.Option.origin: MTKTextureLoader.Origin.bottomLeft,\n MTKTextureLoader.Option.SRGB: false // yes and image washed out\n ])\n return texture\n} catch {\nRun Code Online (Sandbox Code Playgroud)\n请注意,您需要将原点设置为bottomLeft。
\n除非您打算真正深入学习Metal,否则您将进行大量实验并提出问题。当您花费数小时尝试让代码执行您想要的操作时,拥有一个测试工具应用程序将被证明是无价的。
\n任何 Apple 代码肯定都充满了 GLKVector、GLKMatrices 和相关函数。不幸的是,没有工具可以转换它们\xe2\x80\x94,你必须逐行手动完成,有时没有simd等效项。有时我使用 Xcode 的搜索和替换,但不经常使用。
\n首先,要获取 simd 宏,请将其添加到源文件中:import simd
\n我应该指出,Dash在挖掘simd方面提供了巨大的帮助。
\n上面引用的两个函数:
\nfunc simd_make_look_at_float4x4(\n eyeX: Float,\n eyeY: Float,\n eyeZ: Float,\n centerX: Float,\n centerY: Float,\n centerZ: Float,\n upX: Float,\n upY: Float,\n upZ: Float\n) -> simd_float4x4 {\n // /sf/ask/633736421/\n let ev = simd_float3(eyeX, eyeY, eyeZ)\n let cv = simd_float3(centerX, centerY, centerZ)\n let uv = simd_float3(upX, upY, upZ)\n\n let subbed = ev - cv\n let n = simd_normalize(subbed)\n\n let cross_p = simd_cross(uv, n)\n let u = simd_normalize(cross_p)\n\n let v = simd_cross(n, u)\n\n let c0: simd_float4 = [u[0], v[0], n[0], 0]\n let c1: simd_float4 = [u[1], v[1], n[1], 0]\n let c2: simd_float4 = [u[2], v[2], n[2], 0]\n\n let v0 = simd_dot(-1*u, ev)\n let v1 = simd_dot(-1*v, ev)\n let v2 = simd_dot(-1*n, ev)\n let c3: simd_float4 = [v0, v1, v2, 1]\n\n let m: simd_float4x4 = simd_float4x4(columns: (c0, c1, c2, c3))\n return m\n}\n\nfunc simd_make_frustum_float4x4(frustum: Float, aspectRatio: Float) -> simd_float4x4 {\n // https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glFrustum.xml\n let left = -frustum\n let right = frustum\n let bottom = -frustum/aspectRatio\n let top = frustum/aspectRatio\n let near = PanoramaView.Z_NEAR\n let far = PanoramaView.Z_FAR\n\n let m00 = (2.0 * near) / (right - left)\n\n let m11 = (2.0 * near) / (top - bottom)\n\n let m20 = (right + left) / (right - left)\n let m21 = (top + bottom) / (top - bottom)\n let m22 = -1 * (far + near) / (far - near)\n let m23 = Float(-1)\n\n let m32 = -1 * (2 * far * near) / (far - near)\n\n let c0: simd_float4 = [m00, 0, 0, 0]\n let c1: simd_float4 = [0, m11, 0, 0]\n let c2: simd_float4 = [m20, m21, m22, m23]\n let c3: simd_float4 = [0, 0, m32, 0]\n let m = simd_float4x4(columns: (c0, c1, c2, c3))\n\n return m\n}\n\n// Translated from the original Panorama code\nfunc simd_make_quaternion_from_two_vectors(_ u: simd_float3, _ v: simd_float3) -> simd_quatf {\n let w: simd_float3 = simd_cross(u, v)\n var q: simd_quatf = simd_quaternion(w.x, w.y, w.z, simd_dot(u, v))\n q.real += q.length\n return q.normalized\n}\nRun Code Online (Sandbox Code Playgroud)\n这些函数可以在前面提到的 Panorama 存储库的GLK-Metal-Tools.swift文件中找到。如果按照建议您在控制器仅是simd后来回转换,则可以在慢慢删除GLK时将它们放在您的视图中。
\nfunc glkV3_to_simd(_ v3: GLKVector3) -> simd_float3 {\n let v: simd_float3 = simd_make_float3(v3.x, v3.y, v3.z)\n return v\n}\nfunc simd3_to_glk(_ v3: simd_float3) -> GLKVector3 {\n let v = GLKVector3Make(v3[0], v3[1], v3[2])\n return v\n}\n\nfunc glkV4_to_simd(_ v3: GLKVector4) -> simd_float4 {\n let v: simd_float4 = simd_make_float4(v3.x, v3.y, v3.z, v3.w)\n return v\n}\n\nfunc simd4x4_to_glk(_ m: simd_float4x4) -> GLKMatrix4 {\n var array: [GLKVector4] = []\n for i in 0..<4 {\n let fv: simd_float4 = m[i]\n let v: GLKVector4 = GLKVector4Make(fv[0], fv[1], fv[2], fv[3]);\n array.append(v)\n }\n let mg: GLKMatrix4 = GLKMatrix4MakeWithColumns(array[0], array[1], array[2], array[3]);\n return mg;\n}\nfunc glkm4_to_simd(_ m: GLKMatrix4) -> simd_float4x4 {\n var array: [simd_float4] = []\n for i in 0..<4 {\n let fv: GLKVector4 = GLKMatrix4GetColumn(m, Int32(i))\n let v: simd_float4 = simd_make_float4(fv[0], fv[1], fv[2], fv[3]);\n array.append(v)\n }\n let ms: simd_float4x4 = simd_matrix(array[0], array[1], array[2], array[3]);\n return ms;\n}\nRun Code Online (Sandbox Code Playgroud)\n我在开发过程中使用这些打印例程来检查各种值,您可能会发现它们也很有用:
\nfunc print4x4SIMD(\n msg: String,\n m: simd_float4x4\n) {\n var s = ""\n s += "---COL: \\(msg)\\n"\n\n let (c0, c1, c2, c3) = m.columns\n\n s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf\\n", 0, c0[0], c0[1], c0[2], c0[3])\n s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf\\n", 1, c1[0], c1[1], c1[2], c1[3])\n s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf\\n", 2, c2[0], c2[1], c2[2], c2[3])\n s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf\\n", 3, c3[0], c3[1], c3[2], c3[3])\n\n print("\\n\\(s)\\n")\n}\nfunc print4x4GLK(\n msg: String,\n m: GLKMatrix4\n) {\n var s = ""\n s += "---COL: \\(msg)\\n"\n\n s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf\\n", 0, m.m00, m.m01, m.m02, m.m03)\n s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf\\n", 1, m.m10, m.m11, m.m12, m.m13)\n s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf\\n", 2, m.m20, m.m21, m.m22, m.m23)\n s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf\\n", 3, m.m30, m.m31, m.m32, m.m33)\n\n print("\\n\\(s)\\n")\n}\n\nRun Code Online (Sandbox Code Playgroud)\n虽然我还没有使用过这个,但有一天我可能会需要它(它未经测试):
\nfunc matrix_from_rotation(radians: Float, v _v: simd_float3) -> simd_float4x4 {\n // https://www.haroldserrano.com/blog/rotating-a-2d-object-using-metal\n let v: simd_float3 = simd_normalize(_v)\n let cos: Float = cos(radians)\n let cosp: Float = 1 - cos\n let sin: Float = sin(radians)\n\n let col0 = simd_float4(\n cos + cosp * v.x * v.x,\n cosp * v.x * v.y + v.z * sin,\n cosp * v.x * v.z - v.y * sin,\n 0\n )\n\n let col1 = simd_float4(\n cosp * v.x * v.y - v.z * sin,\n cos + cosp * v.y * v.y,\n cosp * v.y * v.z + v.x * sin,\n 0.0\n )\n\n let col2 = simd_float4(\n cosp * v.x * v.z + v.y * sin,\n cosp * v.y * v.z - v.x * sin,\n cos + cosp * v.z * v.z,\n 0.0\n )\n\n let col3 = simd_float4(0, 0, 0, 1)\n\n let m: simd_float4x4 = simd_float4x4(columns: (col0, col1, col2, col3))\n return m\n}\nRun Code Online (Sandbox Code Playgroud)\n这个项目花了我大约半年的时间,每周工作一个周末,但它却持续了 18 个月。原因是:我花了很多天没有任何进展,得到奇怪的损坏的输出,或者没有输出,当我最终得到在Metal中显示的主要视图时,我把项目收起来了。我太累了,无法继续下去。
\n也就是说,iOS 的末日即将结束,而且随着岁月的流逝,那个末日也越来越近了。
\n当我让Metal处理 GLK 向量和矩阵时,我原本打算停止,但 Warren Moore 敦促我现在转换为simd。
\n当我终于构建了公司的应用程序并且没有与GLKit相关的单个编译器警告时,那是一个纯粹的狂喜时刻!
\n