我如何以编程方式切换到 Sprite Kit 中的 openGL 以适应 iPad2 等较旧的设备

How can I programmatically switch to openGL in Sprite Kit to adopt to older devices like iPad2

我正在使用 Sprite Kit 开发游戏。由于 iOS9 SpriteKit 使用 Metal 作为着色器后端。 SK 和着色器效果很好。但是如果我在 iPad2 上测试它,它就不再工作了。我读过这个问题并且知道 iPad2 不支持 Metal。在这种情况下,现在我想回退到打开 GL 以提供 GLES 着色器。

我可以像这样以编程方式测试 "Metal" 是否可用 (Swift 2):

    /**
     Returns true if the executing device supports metal.
     */
    var metalAvailable:Bool {

        get {

            struct Static {

                static var metalAvailable : Bool = false
                static var metalNeedsToBeTested : Bool = true
            }

            if Static.metalNeedsToBeTested {

                let device = MTLCreateSystemDefaultDevice()
                Static.metalAvailable = (device != nil)
            }
            return Static.metalAvailable
        }
    }

我知道可以在应用程序的 plist 中设置兼容模式:

在这种情况下,SpriteKit 始终使用 openGL。这不是我想使用的。我希望我的应用程序始终使用金属,如果未检测到金属设备,则返回到 openGL。

SpriteKit 或 UIKit 或 API 中是否有任何选项可以让我以编程方式切换到 "PrefersOpenGL" 选项?

提前致谢,

杰克

总结

我找到了解决办法。最后是我的错。 SpriteKit 肯定会自动回退到 openGL。 GLES 着色器语言不如 Metal 宽容。这就是问题的来源。在 openGL-shaders 中,您必须在每个数字中设置小数点。不幸的是,着色器编译器在编译后没有告诉我。另一个问题是,有时旧的着色器构建会与捆绑包捆绑在一起。在测试着色器之前执行 "clean"。

这就是处理两种着色器和检测 Metal / openGL 的方法:

检测Metal是否可用

这个小帮手可以放在您的代码中的任何位置。它可以帮助您在第一次使用时检测到 Metal,并让您有机会根据配置一次执行自定义代码。

Headers:

#import SpriteKit
#import Metal

代码:

/**
 Detect Metal. Returns true if the device supports metal.
 */
var metalAvailable:Bool {

    get {

        struct Static {

            static var metalAvailable : Bool = false
            static var metalNeedsToBeTested : Bool = true
        }

        if Static.metalNeedsToBeTested {

            Static.metalNeedsToBeTested = false
            let device = MTLCreateSystemDefaultDevice()
            Static.metalAvailable = (device != nil)
            if Static.metalAvailable {

               // Do sth. to init Metal code, if needed
            } else {

               // Do sth. to init openGL code, if needed
            }
        }
        return Static.metalAvailable
    }
}

在 Sprite Kit 中创建着色器

像往常一样使用精灵工具包创建着色器。

let shaderContainer = SKSpriteNode()
shaderContainer.position = CGPoint(x:self.frame.size.width/2, y:self.frame.size.height/2)
shaderContainer.size = CGSize(width:self.frame.size.width, height:self.frame.size.height)
self.backgroundNode.addChild(shaderContainer)

let bgShader:SKShader
// Test if metal is available
if self.metalAvailable {

  bgShader = SKShader(fileNamed:"plasma.fsh")
} else {

  NSLog("Falling back to openGL")
  bgShader = SKShader(fileNamed:"plasmaGL.fsh")
}

// Add your uniforms. OpenGL needs the size of the frame to normalize
// The coordinates. This is why we always use the size uniform        
bgShader.uniforms = [
   SKUniform(name: "size", floatVector2:GLKVector2Make(1920.0, 1024.0))
]

shaderContainer.shader = bgShader

如您所见,根据检测到的配置,正在加载另一个着色器文件。 openGL 着色器需要额外的统一尺寸,因为符号 v_tex_coord 在 openGL 中不可用。如果你不在 Metal 中使用 size uniform,你可以将 uniforms 语句移到 if 块中或者忽略它。不用金属不抱怨

金属着色器:plasma.fsh

#define  M_PI 3.1415926535897932384626433832795
#define frequency  1 // Metal is less sensitive to number types. 
#define colorDepth 2 // Numbers without decimal point make problems with openGL
void main(void) {

    vec2 uv = v_tex_coord; // Normalized coordinates in Metal shaders

    float red = ((sin((uv.x + u_time * 0.01) * M_PI * frequency) * cos((uv.y + u_time * 0.03) * M_PI * frequency) + 1) / colorDepth) + (colorDepth / 2.75) - (2 / 2.75);

    gl_FragColor = vec4(red, uv.x, u_time, 1.0);
}

在 Metal 着色器中,您可以简单地读取归一化坐标。如果您愿意,可以使用 size 来重建图像坐标。然而,金属对小数点更宽容。如您所见,有些数字在这里没有小数点。

打开 GL 着色器:plasmaGL.fsh

// OPEN GL shaders NEED the decimal point in numbers. so never use 1 but 1. or 1.0
#define  M_PI 3.1415926535897932384626433832795
#define frequency  1.0  // This number must have a decimal point
#define colorDepth 2.0  // Same here.
void main(void) {

    vec2 uv = gl_FragCoord.xy / size.xy; // Frame coordinates in openGL

    // This formula is always using numbers with decimal points. 
    // Compare it to the metal shader. Two numbers of the metal
    // have no decimal point. If you cut copy paste the metal shader 
    // formula to the GL shader it will not work!
    float red = ((sin((uv.x + u_time * 0.01) * M_PI * frequency) * cos((uv.y + u_time * 0.03) * M_PI * frequency) + 1.0) / colorDepth) + (colorDepth / 2.75) - (2.0 / 2.75);

    gl_FragColor = vec4(red, uv.x, u_time, 1.0);
}

展望

测试两个系统和创建两个着色器需要更多工作。但只要我们从 GL 过渡到 Metal,这就是测试应该使用哪种着色器的好方法。 iOS 模拟器也不支持 Metal。这意味着您可以使用 iOS 和 tvOS 模拟器测试 openGL 行为。

如果您为 AppleTV 开发,那么这种方法非常方便,因为 openGL 着色器始终与 Metal 配合使用。您只需将 gl_FragCoord.xy / size.xy 替换为 v_tex_coord。如果您 运行 模拟器上的代码,您将看到 openGL 代码,如果您 运行 AppleTV 目标上的代码,您将看到平滑的金属着色器。

对所有 swift 开发人员的另一个提示:永远不要忘记带有着色器的行末尾的分号 ;-)

另一个陷阱正在施法

金属: int intVal = (int) uv.x; 浮动 a = (浮动) intVal;

打开总帐: int intVal = int(uv.x); 浮动 a = 浮动 (intVal);

希望能帮到大家。

干杯,

杰克