关于Cocos2dx-JS在iOS8以上系统调用OpenGL进入后台时崩溃的解决办法
这次发布新版本后没有预料到的crash是下面这些,并且在遇到之初十分没有头绪:
有这样的:
AGXGLDriver glrAGXRenderVertexArray(GLDContextRec*, unsigned int, unsigned int, int, int, unsigned int, void const*, int…
还有这样的:
IMGSGX543RC2GLDriver glrSGXRenderVertexArray IMGSGX543RC2GLDriver glrSGXRenderVertexArray
还有最多的显示方式是这样的:
WebCore WebCore::GraphicsContext3D::endPaint()
但是他们最终crash的原因却是一样的
Crashed: WebThread EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0x00000001
在web thread中遇到了一个空指针,并且可以肯定是内部的代码作祟,crash log基本也是相同的
Thread : Crashed: WebThread 0 libGPUSupportMercury.dylib 0x2980d776 gpus_ReturnNotPermittedKillClient + 9 1 libGPUSupportMercury.dylib 0x2980e251 gpusSubmitDataBuffers + 120 2 IMGSGX543RC2GLDriver 0x20ffcddd glrSGXRenderVertexArray + 4120 3 GLEngine 0x25130b9b glDrawArrays_ACC_ES2Exec + 358 4 WebCore 0x2ed1b9c5 WebCore::WebGLRenderingContext::drawArrays(unsigned int, int, int, int&) + 128 5 WebCore 0x2ea15bff WebCore::jsWebGLRenderingContextPrototypeFunctionDrawArrays(JSC::ExecState*) + 466 6 JavaScriptCore 0x23b15133 llint_entry + 21314 7 JavaScriptCore 0x23b14cd9 llint_entry + 20200 8 JavaScriptCore 0x23b14cd9 llint_entry + 20200 9 JavaScriptCore 0x23b14cd9 llint_entry + 20200 10 JavaScriptCore 0x23b14cd9 llint_entry + 20200 11 JavaScriptCore 0x23b14cd9 llint_entry + 20200 12 JavaScriptCore 0x23b0fbdf callToJavaScript + 334 13 JavaScriptCore 0x23a9f3b5 JSC::JITCode::execute(JSC::VM*, JSC::ProtoCallFrame*) + 36 14 JavaScriptCore 0x238ebda7 JSC::Interpreter::executeCall(JSC::ExecState*, JSC::JSObject*, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) + 350 15 JavaScriptCore 0x239e10dd JSC::call(JSC::ExecState*, JSC::JSValue, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, JSC::JSValue*) + 64 16 WebCore 0x2e48c7f3 WebCore::JSCallbackData::invokeCallback(JSC::JSValue, JSC::MarkedArgumentBuffer&, bool*) + 426
原因在于嵌入的webview中使用到了Cocos2dx的JS框架,调用了WebGL做图形处理,而WebGL在系统里会调用OpenGL ES的API。根据苹果官方的说明:
https://developer.apple.com/library/ios/qa/qa1766/_index.html
http://developer.apple.com/library/ios/#documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/ImplementingaMultitasking-awareOpenGLESApplication/ImplementingaMultitasking-awareOpenGLESApplication.html#//apple_ref/doc/uid/TP40008793-CH5-SW1
Q: My OpenGL ES application crashes when moving to the background. How do I fix it?
调用了OpenGL ES的程序在退出到后台的时候crash怎么处理
A: If your OpenGL ES application crashes when moving to the background, and you get a crash report that contains a stack trace ending with libGPUSupportMercury.dylib: gpus_ReturnNotPermittedKillClient + 0 as shown in Listing 1, it indicates that the application has attempted to do rendering with OpenGL ES in the background.
Listing 1 Stack trace
Thread 0 name: Dispatch queue: com.apple.main-thread Thread 0 Crashed: 0 libGPUSupportMercury.dylib 0x30570094 gpus_ReturnNotPermittedKillClient + 0 1 libGPUSupportMercury.dylib 0x305700ae gpus_KillClient ( ) 2 libGPUSupportMercury.dylib 0x305705ba gpusSubmitDMABuffers ( ) 3 IMGSGX535GLDriver 0x34bd29b8 SubmitPacketsIfAny ( ) 4 IMGSGX535GLDriver 0x34bd2ad0 glrFlushContextToken ( ) 5 GLEngine 0x37719c4a gliPresentViewES ( ) 6 OpenGLES 0x323df6b4 -[EAGLContext presentRenderbuffer:] ( ) ... ...
An OpenGL ES application will be terminated if it attempts to execute OpenGL ES commands in the background. Your application must ensure that all previously submitted commands have been finished and then stop rendering prior to moving into the background. See details about how to achieve this in the Implementing a Multitasking-aware OpenGL ES Application chapter of the OpenGL ES Programming Guide for iOS.
如果你的程序调用了OpenGL ES并且在后台还尝试执行OpenGL的命令,他会被强制终结。你的程序需要在进入后台之前确认所有的OpenGL命令已经被执行完成并且停止继续执行。
在详细文档里,苹果写到:
An OpenGL ES app must perform additional work when it is moved into the background. If an app handles these tasks improperly, it may be terminated by iOS instead. Also, an app may want to free OpenGL ES resources so that those resources are made available to the foreground app.
如果你的程序调用了OpenGL ES,那么在进入后台前需要执行额外操作。如果你的程序没有合适的处理这个任务,系统会终结它。并且你将需要释放OpenGl的资源让前台的程序能够使用更多的资源。
Background Apps May Not Execute Commands on the Graphics Hardware
An OpenGL ES app is terminated if it attempts to execute OpenGL ES commands on the graphics hardware. iOS prevents background apps from accessing the graphics processor so that the frontmost app is always able to present a great experience to the user. Your app can be terminated not only if it makes OpenGL ES calls while in the background but also if previously submitted commands are flushed to the GPU while in the background. Your app must ensure that all previously submitted commands have finished executing before moving into the background.
苹果对于UI处理的资源是管理的十分严格的,对于图形计算的资源来说,如果是用户自己调用的OpenGL命令,系统会禁止处在后台的程序去调用这些命令,因为资源要全部的留给前台的程序去呈现更好的UI。系统的一些自己调用OpenGL的控件自身会处理这些。
If you use a GLKit view and view controller, and only submit OpenGL ES commands during your drawing method, your app automatically behaves correctly when it moves to the background. The GLKViewController class, by default, pauses its animation timer when your app becomes inactive, ensuring that your drawing method is not called.
If you do not use GLKit views or view controllers or if you submit OpenGL ES commands outside a GLKView drawing method, you must take the following steps to ensure that your app is not terminated in the background:
In your app delegate’s applicationWillResignActive: method, your app should stop its animation timer (if any), place itself into a known good state, and then call the glFinish function.
In your app delegate’s applicationDidEnterBackground: method, your app may want to delete some of its OpenGL ES objects to make memory and resources available to the foreground app. Call the glFinish function to ensure that the resources are removed immediately.
After your app exits its applicationDidEnterBackground: method, it must not make any new OpenGL ES calls. If it makes an OpenGL ES call, it is terminated by iOS.
In your app’s applicationWillEnterForeground: method, re-create any objects and restart your animation timer.
To summarize, your app needs to call the glFinish function to ensure that all previously submitted commands are drained from the command buffer and are executed by OpenGL ES. After it moves into the background, you must avoid all use of OpenGL ES until it moves back into the foreground.
当应用退出到后台时如果程序仍然在调用OpenGL的API,那么程序会被强制终结。文档没有说明系统版本问题,但是在iOS8以上的系统上测试确实是必现的一个crash,但是由于是在退出后crash(其实也就是下一次进入应用是重新启动了,有可能在刚打开的时候会闪现一下之前的画面),用户不易察觉。奇怪的是iOS7系统并不会出现这个问题,并且在后台所有的crash日志里也都100%是iOS8以上的用户报告了这一crash。
但是这些都是对于一个在自己的程序里开发了OpenGL ES接口的开发者来说的,我的程序里没有任何调用了OpenGL的地方,只有Cocos2dx-JS访问了OpenGL的接口,我在百度上搜到一篇新闻,说是Cocos2dx-JS从iOS8开始支持WebGL,大幅提升了图像的动画和显示质量,我开始怀疑系统版本的问题和底层的图像处理方式有关系。
报道出处:
http://www.cocoachina.com/cocos/20140928/9787.html
在Cocos2dx的游戏工程配置文件里:
• renderMode: Web引擎绘制模式,仅服务于Web引擎,可能的取值如下:
• 0 – 由引擎自动选择绘制模式
• 1 – 强制使用Canvas绘制模式
• 2 – 强制使用WebGL绘制模式,但是实际上WebGL仍然可能会在一些移动浏览器上被忽略而自动使用Canvas绘制模式
事实上,对于Web网页上的动画处理,在iOS7上的处理是用CANVAS的绘制形式,而iOS8开始采用了更好的WebGL的处理方式,但是这种处理方式给开发者带来的任务就是要在应用里合理的和Cocos2dx-JS的代码交互并且控制好对OpenGL接口的调用。
如果你是原生的调用OpenGL或者原生的Cocos2dx开发,那么需要遵循的原则可以参考苹果的文档和这篇StackOverFlow:
http://stackoverflow.com/questions/10620287/opengl-es-crash-on-move-background-ios-5-1
如果你是通过Cocos2dx-JS来调用OpenGL,那么你需要通过JavaScriptCore来执行页面的JS中可能有的各类Pause方法,在应用退出到后台之前阻止JS继续调用WebGL来绘制动画。
关于Objective-C如何与Javascript交互,可以参考这篇博客:
http://www.chentoo.com/?p=191
最后修改了Cocos2dx中的project.json配置文件,将rendermode强制改为1,在iOS8上运行虽然画质略微差,但是无需更底层的OpenGL访问控制,运行无误,Crash Solved!