在后台线程中尝试 load/setup 场景导致 "OpenGL error 0x0502 in -[CCSprite draw] 530"

Trying to load/setup scene in background thread results in "OpenGL error 0x0502 in -[CCSprite draw] 530"

我有一个 cocos2d v2.x 应用程序,它的场景有很多精灵、节点、配置、数据等...加载起来非常昂贵,太多了将场景添加到导演时,会有 1/2 到 1 秒的暂停,导致当前 运行 动画冻结,直到场景加载完毕。我分析了最慢的方法并尝试在后台线程中异步执行它们并在加载时显示进度微调器。

我的实现是这样的:

-(void)performAsyncLoad {
    self.progressSpinner.visible = YES;
    self.containerForLoadedStuff.visible = NO;
    self.mainContext = [EAGLContext currentContext];

    NSOperationQueue *queue = [NSOperationQueue new];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                                            selector:@selector(loadDependencies)
                                                                              object:nil];
    [queue addOperation:operation];
}

-(void)loadDependencies {
    @autoreleasepool {
        glFlush();
        EAGLSharegroup *shareGroup = [[(CCGLView*)[[CCDirector sharedDirector] view] context] sharegroup];
        EAGLContext *context = [[EAGLContext alloc] initWithAPI:[[EAGLContext currentContext] API] sharegroup:shareGroup];
        [EAGLContext setCurrentContext:context];

        // ... expensive stuff here
        // [self.containerForLoadedStuff addChild:sprites, etc...]

        [self performSelectorOnMainThread:@selector(done) withObject:nil waitUntilDone:NO];
    }
}

-(void)done {
    glFlush();
    [EAGLContext setCurrentContext:self.mainContext];
    self.progressSpinner.visible = NO;
    self.containerForLoadedStuff.visible = YES;
}

不幸的是,这不起作用,一旦调用该操作,它就会在 CCTextureAtlas 的第 523 行 EXC_BAD_ACCESS 上崩溃

glDrawElements(GL_TRIANGLES, (GLsizei) n*6, GL_UNSIGNED_SHORT, (GLvoid*) (start*6*sizeof(_indices[0])) );

并且控制台日志显示数十亿:

OpenGL error 0x0502 in -[CCSprite draw] 530

我做错了什么?

更新

我更改了代码以执行以下操作:

dispatch_queue_t queue = dispatch_queue_create("myqueue", NULL);
CCGLView *view = (CCGLView*)[[Director sharedDirector] view];
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup:[[view context] sharegroup]];

dispatch_async(queue, ^{
   [EAGLContext setCurrentContext:context];

   // expensive calls

   glFlush();
   [self performSelector:@selector(done) onThread:[[CCDirector sharedDirector] runningThread] withObject:nil waitUntilDone:NO];
   [EAGLContext setCurrentContext:nil];
});

它停止了崩溃,一切都有效,但我仍然得到十亿:

OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530

知道这些错误发生的原因以及如何阻止它们吗?

又一更新

这毫无意义...显然这些错误来自将精灵添加到 CCSpriteBatchNode。如果我将它们放在常规的 CCNode 上,那么一切正常。什么鬼!?!?!?!?!

和最后一次更新*

看来是一堆废话,我就是不明白。我已经设法让这些错误消失了 98%,但它们似乎仍然断断续续地随机发生。我做了很多调试器和试错测试,发现这段代码:

-(void)loadDependencies {
    dispatch_queue_t queue = dispatch_queue_create("myqueue", NULL);
    CCGLView *view = (CCGLView*)[[Director sharedDirector] view];
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup:[[view context] sharegroup]];

    dispatch_async(queue, ^{
       [EAGLContext setCurrentContext:context];

       [self.myObject doExpensiveStuff];

       glFlush();
       [self performSelector:@selector(done) onThread:[[CCDirector sharedDirector] runningThread] withObject:nil waitUntilDone:NO];
       [EAGLContext setCurrentContext:nil];
    });
}

-(void)done {
    [self.delegate completedAsyncStuff];
}

导致随机崩溃——通常 cocos removeFromParent 试图删除无效索引处的四边形...所以我尝试在处理对象之前暂停该对象...

//... background thread stuff:
[self.myObject pauseSchedulerAndActions];
[self.myObject doExpensiveStuff];
[self.myObject resumeSchedulerAndActions];

然后它不再崩溃,而是将大量的 OpenGL 错误 0x0502 放入日志中的 -[CCSprite draw] 530 ......

然后我做了一些极端的日志记录,试图找到这些错误发生的位置...

... // previous code above... etc
       NSLog(@"gl flush...");
       glFlush();
       NSLog(@"after gl flush...");
       [self performSelector:@selector(done) onThread:[[CCDirector sharedDirector] runningThread] withObject:nil waitUntilDone:NO];
       [EAGLContext setCurrentContext:nil];
    });
}

-(void)done {
    NSLog(@"done scheduling notify delegate");
    [self scheduleOnce:@selector(notifyDelegate) delay:1];
}

-(void)notifyDelegate {
    NSLog(@"about to notify delegate");
    [self.delegate completedAsyncStuff];
}

在我的日志中我看到:

gl flush
after gl flush
done scheduling notify delegate
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
OpenGL error 0x0502 in -[CCSprite draw] 530
about to notify delegate

所以这些错误发生在 cocos 等待预定选择器触发时????我勒个去!?我受不了了,没人能帮我,所以是时候悬赏了。

你能告诉我你为什么那么努力地做你想做的事吗?

如果我是你,我会执行以下操作来异步加载场景。

  1. 加载带有加载指示器的中间场景,或将其添加到当前场景。 + 创建一个新的场景对象。
  2. 为新场景调度异步昂贵的方法。
  3. 完成后用 CCDirector 替换场景,调度 tihs 操作同步

像现在一样使用 GCD。相信我,您真的不想在加载场景时绘制场景。

这是我的做法:

加载场景

-(void) onEnterTransitionDidFinish {
    [super onEnterTransitionDidFinish];
    [NSThread detachNewThreadSelector:@selector(loadGameSceneInAnotherThread) toTarget:self withObject:nil];
}

- (void) loadGameSceneInAnotherThread {
    NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
    CCGLView *view = (CCGLView*)[[CCDirector sharedDirector] view];
    EAGLContext *auxGLcontext = [[EAGLContext alloc]
                                 initWithAPI:kEAGLRenderingAPIOpenGLES2
                                 sharegroup:[[view context] sharegroup]];

    if( [EAGLContext setCurrentContext:auxGLcontext] ) {
       self.gameScene = [GameScene sceneWithLevelName:levelName];
       self.gameLoadingDone = YES;
       glFlush();
       [EAGLContext setCurrentContext:nil];
    }
    [auxGLcontext release];
    [autoreleasepool release];
}


//this method ticks every 0.5sec
-(void) checkIfGameSceneLoaded {
    if (self.gameLoadingDone) {
        [self unschedule:@selector(checkIfGameSceneLoaded)];
        [[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:0.75 scene:self.gameScene]];
    }
}

但是,我不建议为场景加载背景...因为 cocos2d 并非真正设计用于这样做...