【问题标题】:How do I convert an OpenGL GLKView to a MTLKit Metal based View?如何将 OpenGL GLKView 转换为基于 MTLKit Metal 的视图?
【发布时间】:2023-02-25 04:36:11
【问题描述】:

几年前,Apple 开始警告任何使用GL套件在他们的应用程序中OpenGL正在离开:

警告:不推荐使用 OpenGLES。考虑迁移到 Metal
警告:不推荐使用 GLKit。考虑改为迁移到 MetalKit

我的应用程序使用复杂的 OpenGL 类,我也不知道OpenGL或者金属. Apple 有一些关于此的 WWDC 会议,但他们针对的是OpenGL专家。由于 Apple 有一天会删除 OpenGL,所以我想在我只有几个月的时间之前开始它。我应该怎么办?

【问题讨论】:

  • 很好的问答,谢谢!

标签: ios opengl metal metalkit


【解决方案1】:

tldr;

一旦我开始在 iOS12 中看到构建错误消息:

警告:不推荐使用 OpenGLES。考虑迁移到 Metal
警告:不推荐使用 GLKit。考虑改为迁移到 MetalKit

我知道我必须做点什么。谁知道苹果什么时候会移除OpenGLGL套件?相信我,您不想等到只有几个月的时间才转换为金属,因为这个过程绝不是直截了当的。

下面是我用来转换的过程 一个目标-C/OpenGL查看金属.这是一个漫长而艰巨的过程,数次 我把头伏在桌子上,沮丧地哭了起来。

我采取的基本步骤是我建议其他人也采用的步骤:

  1. 删除所有业务逻辑和任何与OpenGL从视图中,并根据需要重组主应用程序。
  2. 创建一个测试线束您将用于转换的应用程序,并绝对将其置于版本控制之下。
  3. 添加OpenGL查看测试线束.
  4. 一旦视图控制器可以驱动视图,你可以看到它,你就可以开始过渡了。

    就我而言,我遇到了三个障碍:将视图转换为迅速, 重新创建功能金属, 然后全部替换GLK向量和矩阵值和操作SIMD.

    我的建议是:

    1. 转换任何目标C迅速(我用的是Swiftify,有限翻译免费,不过我有订阅)
    2. 添加一个MTK视图测试线束, 并将代码开关放在视图控制器以便交替查看任一视图(比较两者对我有很大帮助)。
    3. 因为我也不知道OpenGL或者金属,我花了很多时间下载开源金属项目和教程。
    4. 创建金属样板(基于示例/教程)以及着色器.
    5. 在你的桌子上放一个垫子,这样当你沮丧地敲头试图得到任何事物显示在金属认为你没有严重伤害自己。
    6. 翻过山后,将GLK值/操作SIMD,利用稍后显示的翻译功能。

      我怎么强调都不为过——每次更改一些操作并测试它们时都要提交!你肯定会破坏东西,这样你就可以引用早期的工作代码。

      测试线束将被证明是有用的,因为您可能会发现时序更改会导致不良行为。在我的例子中,我创建了两个线束,第二个线束中包含更多应用程序代码,因此我可以更好地调试实际使用情况。

      项目

      我 fork 了一个开源项目Panorama。这掌握分支包含金属/simd代码,以及Swift-OpenGL分支包含原始目标C代码连同迅速转换。这让读者可以并排比较两者。但是,您不需要参考它来了解如何转换OpenGL代码输入目标C迅速, 或转换GL套件向量和矩阵SIMD, 如下。

      目标 C 到 Swift

      OpenGL代码大量使用指针,而这些在迅速.例如:

      GLfloat *m_TexCoordsData;  // treated as an array of pairs of floats
      glTexCoordPointer(2, GL_FLOAT, 0, m_TexCoordsData);
      

      成为

      struct Pointer2 {
          private var array: [SIMD2<Float>]
      
          init(size: Int) {
              let n: SIMD2<Float> = [Float.nan, Float.nan]
              array = Array<SIMD2<Float>>(repeating: n, count: size)
          }
      
          subscript(index: Int) -> SIMD2<Float>{
              get { return array[index] }
              set(newValue) { array[index] = newValue }
          }
      
          mutating func usingRawPointer(block: WithRawPtr) {
              array.withUnsafeBytes { (bufPtr) -> Void in
                  block(bufPtr.baseAddress!)
              }
          }
      }
          private var tPtr: Pointer2   // m_TexCoordsData
          tPtr.usingRawPointer(block: { (ptr) in
              glTexCoordPointer(2, UInt32(GL_FLOAT), 0, ptr)
          })
      

      这一切都在决赛中消失了金属代码。 进行转换也会出现潜在的错误!

      此外,我必须将许多值转换(转换)为更严格的 Swift 类型:

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
      

      成为

      glTexParameteri(UInt32(GL_TEXTURE_2D), UInt32(GL_TEXTURE_WRAP_S), GLint(GL_REPEAT))
      

      这个过程很乏味。但是,那迅速恕我直言,代码更精简且更易于阅读。

      几个功能很容易翻译:

      GLKQuaternion GLKQuaternionFromTwoVectors(GLKVector3 u, GLKVector3 v) {
          GLKVector3 w = GLKVector3CrossProduct(u, v);
          GLKQuaternion q = GLKQuaternionMake(w.x, w.y, w.z, GLKVector3DotProduct(u, v));
          q.w += GLKQuaternionLength(q);
          return GLKQuaternionNormalize(q);
      }
      

      成为

      func GLKQuaternionFromTwoVectors(_ u: GLKVector3, _ v: GLKVector3) -> GLKQuaternion {
          let w = GLKVector3CrossProduct(u, v)
          var q = GLKQuaternionMake(w.x, w.y, w.z, GLKVector3DotProduct(u, v))
          q.w += GLKQuaternionLength(q)
          return GLKQuaternionNormalize(q)
      }
      

      稍后你会看到翻译成SIMD没那么容易。

      OpenGL 到金属

      不幸的是,这里没有魔法棒。 Apple 在这方面有一些 WWDC 会议,但他们并没有真正启发我。金属使用两种类型的内核:计算和着色器,计算更容易。然而,在我的例子中,我不得不使用着色器,这对我来说更难掌握。

      金属资源

      如果您一无所知,这是一个很好的起点金属这是 Ray Wenderlich 网站上的 Metal Tutorial 吗?第二篇文章更有帮助:Ray Wenderlich 网站上的Moving From OpenGL to Metal。两者都有大量参考更多金属材料。

      我发现有帮助的另外两篇优秀文章:Donald Pinckney's Blog (Older)。另一位乐于助人的作者:Alex Barbulescu

      写这本关于 Metal 的书的人是Warren Moore。他的书和文章是无价的!

      要记住的事情

      OpenGL使用一个剪辑空间-1 到 1 (z值)。您需要在着色器中考虑到这一点。 Warren Moore 个人建议我确保我的着色器不会返回负数z使用此代码的值:

      v.z = v.z * 0.5 + v.w * 0.5;
      

      这避免了完全重做可能使用负数的 OpenGL 代码的需要z值。

      背景颜色MTL视图不是通过使用该属性设置的,而是设置清除颜色.

      从 App 空间到着色器空间的通信是使用结构完成的,必须分别在每个结构中单独定义。例如,在我的应用程序中这个结构:

      private struct Uniforms {
        let projectionMatrix: simd_float4x4
        let attitudeMatrix: simd_float4x4
      }
      

      在着色器中定义为:

      struct Uniforms {
        float4x4 projectionMatrix;
        float4x4 attitudeMatrix;
      };
      

      这些结构是应用程序定义的。

      纹理

      如果您使用图像来创建纹理,则有点不同金属.这

      NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], GLKTextureLoaderOriginBottomLeft, nil];
      GLKTextureInfo *info=[GLKTextureLoader textureWithContentsOfFile:path options:options error:&error];
      glBindTexture(GL_TEXTURE_2D, info.name);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, IMAGE_SCALING);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, IMAGE_SCALING);
      

      成为

      let loader: MTKTextureLoader = MTKTextureLoader(device: mtlDevice)
      do {
          let texture = try loader.newTexture(cgImage: cgImage, options: [
              MTKTextureLoader.Option.origin: MTKTextureLoader.Origin.bottomLeft,
              MTKTextureLoader.Option.SRGB: false // yes and image washed out
          ])
          return texture
      } catch {
      

      请注意,您需要将原点设置为 bottomLeft。

      最后评论

      除非你打算真正学习金属深入地,您将进行大量实验并提出问题。有一个测试线束当您花费数小时尝试让您的代码执行您想要的操作时,应用程序将被证明是无价的。

      将 GLK 翻译成 simd

      任何 Apple 代码肯定都充满了 GLKVectors、GLKMatrices 和相关函数。遗憾的是,没有工具可以转换它们——您必须逐行手工完成,而且有时没有SIMD相等的。有时我使用 Xcode 的搜索和替换,但不经常使用。

      GLK -> SIMD

      首先,要获取 simd 宏,请将其添加到您的源文件中:导入simd

      • GLfloat -> 浮动
      • GLint -> Int
      • GLKMatrix4 -> simd_float4(SIMD4 的类型别名)
      • GLKMatrix4Identity -> matrix_identity_float4x4(不好找)
      • GLKMatrix4Invert -> simd_inverse(simd_float4x4)
      • GLKMatrix4Make -> simd_float4x4(simd_float4,simd_float4,simd_float4,simd_float4)
      • GLKMatrix4MakeFrustum -> 无替换,下面提供的功能
      • GLKMatrix4MakeLookAt -> 无替换,下面提供的功能
      • GLKMatrix4MakeWithQuaternion -> simd_matrix4x4(simd_quatf)
      • GLKMatrix4Multiply -> simd_float4x4 * simd_float4x4
      • GLKMatrix4MultiplyVector3 -> 无替换,下面提供的函数
      • GLKMatrix4MultiplyVector4 ->simd_float4x4 * simd_float4
      • GLK四元数 -> simd_quatf
      • GLKQuaternionLength -> simd_quatf.length
      • GLKQuaternionMake -> simd_quaternion(_ x: Float, _y: Float, _ z: Float, _ w: Float)
      • GLKQuaternionNormalize -> simd_quatf.normalized
      • GLKTextureInfo -> MTLTexture
      • GLKVector3 -> simd_float3
      • GLKVector3CrossProduct -> simd_cross(simd_float3, simd_float3)
      • GLKVector3DotProduct -> simd_dot(simd_float3, simd_float3)
      • GLKVector3Make -> simd_make_float3(_ x:浮点数,_y:浮点数,_ z:浮点数)
      • GLKVector3Normalize -> simd_normalize(simd_float3)
      • GLKVector4 -> simd_float4
      • GLKVector4Make -> simd_make_float4(_ x:浮点数,_y:浮点数,_ z:浮点数,_ w:浮点数)

      我应该注意到 Dash 在挖掘SIMD功能。

      上面提到的两个函数:

      func simd_make_look_at_float4x4(
                                      eyeX: Float,
                                      eyeY: Float,
                                      eyeZ: Float,
                                      centerX: Float,
                                      centerY: Float,
                                      centerZ: Float,
                                      upX: Float,
                                      upY: Float,
                                      upZ: Float
      ) -> simd_float4x4 {
          // https://stackoverflow.com/questions/9053377/ios-questions-about-camera-information-within-glkmatrix4makelookat-result
          let ev = simd_float3(eyeX, eyeY, eyeZ)
          let cv = simd_float3(centerX, centerY, centerZ)
          let uv = simd_float3(upX, upY, upZ)
      
          let subbed = ev - cv
          let n = simd_normalize(subbed)
      
          let cross_p = simd_cross(uv, n)
          let u = simd_normalize(cross_p)
      
          let v = simd_cross(n, u)
      
          let c0: simd_float4 = [u[0], v[0], n[0], 0]
          let c1: simd_float4 = [u[1], v[1], n[1], 0]
          let c2: simd_float4 = [u[2], v[2], n[2], 0]
      
          let v0 = simd_dot(-1*u, ev)
          let v1 = simd_dot(-1*v, ev)
          let v2 = simd_dot(-1*n, ev)
          let c3: simd_float4 = [v0, v1, v2, 1]
      
          let m: simd_float4x4 = simd_float4x4(columns: (c0, c1, c2, c3))
          return m
      }
      
      func simd_make_frustum_float4x4(frustum: Float, aspectRatio: Float) -> simd_float4x4 {
          // https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glFrustum.xml
          let left    = -frustum
          let right   = frustum
          let bottom  = -frustum/aspectRatio
          let top     = frustum/aspectRatio
          let near     = PanoramaView.Z_NEAR
          let far      = PanoramaView.Z_FAR
      
          let m00 = (2.0 * near) / (right - left)
      
          let m11 = (2.0 * near) / (top - bottom)
      
          let m20 = (right + left) / (right - left)
          let m21 = (top + bottom) / (top - bottom)
          let m22 = -1 * (far + near) / (far - near)
          let m23 = Float(-1)
      
          let m32 = -1 * (2 * far * near) / (far - near)
      
          let c0: simd_float4 = [m00, 0, 0, 0]
          let c1: simd_float4 = [0, m11, 0, 0]
          let c2: simd_float4 = [m20, m21, m22, m23]
          let c3: simd_float4 = [0, 0, m32, 0]
          let m = simd_float4x4(columns: (c0, c1, c2, c3))
      
          return m
      }
      
      // Translated from the original Panorama code
      func simd_make_quaternion_from_two_vectors(_ u: simd_float3, _ v: simd_float3) -> simd_quatf {
          let w: simd_float3 = simd_cross(u, v)
          var q: simd_quatf = simd_quaternion(w.x, w.y, w.z, simd_dot(u, v))
          q.real += q.length
          return q.normalized
      }
      

      在 GLK 和 simd 之间来回翻译

      这些函数可以在前面提到的 Panorama 存储库中找到,在一个文件中GLK-金属-Tools.swift.如果按照建议您在控制器单独运行后来回翻译SIMD,您可以在慢慢移除GLK代码。

      func glkV3_to_simd(_ v3: GLKVector3) -> simd_float3 {
          let v: simd_float3 = simd_make_float3(v3.x, v3.y, v3.z)
          return v
      }
      func simd3_to_glk(_ v3: simd_float3) -> GLKVector3 {
          let v = GLKVector3Make(v3[0], v3[1], v3[2])
          return v
      }
      
      func glkV4_to_simd(_ v3: GLKVector4) -> simd_float4 {
          let v: simd_float4 = simd_make_float4(v3.x, v3.y, v3.z, v3.w)
          return v
      }
      
      func simd4x4_to_glk(_ m: simd_float4x4) -> GLKMatrix4 {
          var array: [GLKVector4] = []
          for i in 0..<4 {
              let fv: simd_float4 = m[i]
              let v: GLKVector4 = GLKVector4Make(fv[0], fv[1], fv[2], fv[3]);
              array.append(v)
          }
          let mg: GLKMatrix4 = GLKMatrix4MakeWithColumns(array[0], array[1], array[2], array[3]);
          return mg;
      }
      func glkm4_to_simd(_ m: GLKMatrix4) -> simd_float4x4 {
          var array: [simd_float4] = []
          for i in 0..<4 {
              let fv: GLKVector4 = GLKMatrix4GetColumn(m, Int32(i))
              let v: simd_float4 = simd_make_float4(fv[0], fv[1], fv[2], fv[3]);
              array.append(v)
          }
          let ms: simd_float4x4 = simd_matrix(array[0], array[1], array[2], array[3]);
          return ms;
      }
      

      我在开发过程中使用这些打印例程来检查各种值,您可能也会发现它们有用:

      func print4x4SIMD(
          msg: String,
          m: simd_float4x4
      ) {
          var s = ""
          s += "---COL: (msg)
      "
      
          let (c0, c1, c2, c3) = m.columns
      
          s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf
      ", 0, c0[0], c0[1], c0[2], c0[3])
          s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf
      ", 1, c1[0], c1[1], c1[2], c1[3])
          s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf
      ", 2, c2[0], c2[1], c2[2], c2[3])
          s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf
      ", 3, c3[0], c3[1], c3[2], c3[3])
      
          print("
      (s)
      ")
      }
      func print4x4GLK(
          msg: String,
          m: GLKMatrix4
      ) {
          var s = ""
          s += "---COL: (msg)
      "
      
          s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf
      ", 0, m.m00, m.m01, m.m02, m.m03)
          s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf
      ", 1, m.m10, m.m11, m.m12, m.m13)
          s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf
      ", 2, m.m20, m.m21, m.m22, m.m23)
          s += String(format: "[%.2d] %10.4lf %10.4lf %10.4lf %10.4lf
      ", 3, m.m30, m.m31, m.m32, m.m33)
      
          print("
      (s)
      ")
      }
      
      

      simd_float4x4 旋转

      虽然我还没有使用它,但有一天我可能会需要它(未经测试):

      func matrix_from_rotation(radians: Float, v _v: simd_float3) -> simd_float4x4 {
          // https://www.haroldserrano.com/blog/rotating-a-2d-object-using-metal
          let v: simd_float3 = simd_normalize(_v)
          let cos: Float = cos(radians)
          let cosp: Float = 1 - cos
          let sin: Float = sin(radians)
      
          let col0 = simd_float4(
              cos + cosp * v.x * v.x,
              cosp * v.x * v.y + v.z * sin,
              cosp * v.x * v.z - v.y * sin,
              0
          )
      
          let col1 = simd_float4(
              cosp * v.x * v.y - v.z * sin,
              cos + cosp * v.y * v.y,
              cosp * v.y * v.z + v.x * sin,
              0.0
          )
      
          let col2 = simd_float4(
              cosp * v.x * v.z + v.y * sin,
              cosp * v.y * v.z - v.x * sin,
              cos + cosp * v.z * v.z,
              0.0
          )
      
          let col3 = simd_float4(0, 0, 0, 1)
      
          let m: simd_float4x4 = simd_float4x4(columns: (col0, col1, col2, col3))
          return m
      }
      

      结论

      这个项目花了我大约半年的时间,每周工作一个周末,但它跨越了 18 个月。原因:我花了这么多天没有任何进展,得到奇怪的损坏输出,或者没有输出,当我终于得到主要视图显示金属正如它所做的那样,我把这个项目搁置了。我太累了,无法继续下去。

      也就是说,iOS 的末日即将结束,随着岁月的流逝,那个末日越来越近了。

      我本来打算停下来的金属使用 GLK 向量和矩阵,但被敦促转换为SIMD现在由沃伦摩尔。

      当我终于构建了我公司的应用程序并且没有一个编译器警告与GL套件

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 2021-05-30
    • 2020-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多