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

I am developing a game using the Sprite Kit. Because iOS9 SpriteKit uses Metal as a shader server. SK and shaders work great. But if I test it on iPad2, it no longer works. I read about this issue and know that iPad2 does not support Metal. Now I would like to come back to open GL to provide GLES shaders in this case.

I can programmatically check if Metal is available (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 } } 

I know that you can set compatibility mode in the application plist:

  • Edit the Info.plist Application
  • Add PrefersOpenGL key with bool value YES

In this case, SpriteKit always uses openGL. This is not what I would like to use. I want my application to always use metal and just return to openGL if no metal device was detected.

Is there an option in SpriteKit or UIKit or somewhere in the API where I can programmatically switch to "PrefersOpenGL"?

Thanks in advance,

Jack

+5
source share
1 answer

Summary

I have found a solution. In the end, it was my mistake. SpriteKit will certainly automatically revert to openGL. The GLES shader language is less simple than metal. This is where the problem arose. In openGL shaders, you MUST set a decimal point in each number. Unfortunately, the shader compiler did not tell me that after compilation. Another problem was that sometimes old shaders build a stick with a package. Perform clean before testing the shader.

So, here's how to deal with both types of shaders and detect Metal / openGL:

Determine if metal is available

This little helper can be placed anywhere in your code. This helps you detect metal upon first use and gives you the ability to execute your own code, depending on your configuration, once.

Headers:

 #import SpriteKit #import Metal 

code:

 /** 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 } } 

Create a shader in a sprite set

Create a shader, as usual, using a set of sprites.

 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 

As you can see, depending on the detected configuration, another shader file is loaded. OpenGL shaders require an extra form for size because the v_tex_coord character is not available in openGL. If you are not using the size in the Metal form, you can move the form operator to the if block or simply ignore it. Metal does not complain if you do not use it.

Metal Shader: 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); } 

In metal shaders, you can simply read the normalized coordinates. You can use the size to restore the coordinates of the image if you want. However, the metal more forgives decimal points. As you can see, some numbers here do not have decimal points.

Open GL shader: 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); } 

Outlook

This is a lot of work to test both systems and create two shaders. But as long as we move from GL to Metal, this is a good method for checking which type of shader to use. The iOS simulator also does not support Metal. This means that you can test openGL behavior with the iOS simulator and tvOS.

If you are developing for AppleTV, then this approach is really convenient because openGL shaders always work with Metal. You just need to replace gl_FragCoord.xy / size.xy with v_tex_coord. If you run the code on the simulator, you will see the openGL code; if you run it on the AppleTV target, you will see smooth metal shaders.

And one more hint to all fast developers: never forget the semicolon at the end of the line with shaders; -)

Another trap throws.

Metal: int intVal = (int) uv.x; float a = (float) intVal;

Open GL: int intVal = int (uv.x); float a = float (intVal);

Hope I help someone.

Greetings

Jack

+6
source

Source: https://habr.com/ru/post/1244111/


All Articles