5

I have a cocos2d v2.x app which has a scene that has a lot of sprites, nodes, configuration, data, etc... It is quite expensive to load, so much that when adding the scene to the director, there is a 1/2 to 1 second pause, causing the current running animations to freeze until this the scene is loaded. I profiled the slowest methods and am trying to execute them asynchronously in a background thread and display a progress spinner while it loads.

My implementation is something like this:

-(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;
}

Unfortunately this is not working, once the operation is invoked, it crashes with EXC_BAD_ACCESS on line 523 of CCTextureAtlas on

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

and the console log shows billions of:

OpenGL error 0x0502 in -[CCSprite draw] 530

what am I doing wrong?

UPDATE

I changed my code to do:

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];
});

And it stopped the crashing, and everything works, however I still get a billion:

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

Any ideas why these errors are happening and how I can stop them?

YET ANOTHER UPDATE

This makes no sense... apparently these errors come from adding sprites to a CCSpriteBatchNode. If I put those on a regular CCNode then everything works fine. WHAT THE HELL!?!?!?!?!

AND ONE LAST FINAL UPDATE*

It appears that there is just a whole lot of nonsense that I just don't understand. I've managed to make these errors go away 98%, but they still seem to randomly happen extremely intermittently. I did a ton of debugger and trial & error testing, and found that this code:

-(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];
}

Caused random crashes-- usually cocos removeFromParent trying to remove a quad at an invalid index... So then I tried pausing the object before doing work on it..

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

Then it no longer crashed, but put gazillions of those OpenGL error 0x0502 in -[CCSprite draw] 530 in the log......

So then I did some extreme logging to try to find where these errors were happening...

... // 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];
}

In my log I see:

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

So these errors happen when cocos is waiting to fire of a scheduled selector???? What the hell!? I can't stand this anymore, and no one has been able to help me, so it's time for a bounty.

patrick
  • 9,290
  • 13
  • 61
  • 112
  • http://stackoverflow.com/questions/15632690/opengl-error-0x0502-in-ccsprite-draw-530 – PWiggin Dec 12 '15 at 08:19
  • @PWiggin sorry, I don't understand... I'm not using GLBegin(), GLLineWidth(), or glDrawArrays()... I'm simply newing up my ccsprites / ccnodes and adding them as children. Are you saying CCSpriteBatchNodes are using immediate mode? – patrick Dec 12 '15 at 19:27
  • Those are just examples. The important thing is to stick to the Opengl 2.0 API. – PWiggin Dec 12 '15 at 21:47
  • I totally don't understand what you're saying. I am not using any openGL api's directly other than "glFlush()"; which is what all the apple documentation says is required to do when working with multiple contexts/background threads that are sharing resources. Further, cocos2d does glFlush() internally when it does its async image loading in CCTextureCache.m.. I am not doing ANY openGL calls directly other than that, and those OpelGL errors happen regardless of my calling glFlush(); – patrick Dec 12 '15 at 22:14

2 Answers2

5

Here's how I do it:

LoadingScene

-(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]];
    }
}

However, I don't recommend background loading for scenes.. because cocos2d not really designed to do so...

KAMIKAZE
  • 420
  • 6
  • 27
  • Thank you! I was relying on the child to do the performSelectorOnMainThread in order to inform the parent (via delegation) that it was ready to be added as a child, and that resulted in intermittent errors. Having the parent poll if the child is loaded and ready to be added seems to solve the problem (using your "checkIfGameSceneLoaded" idea) and I am no longer seeing those obnoxious OpenGL Layers. Thanks!!! – patrick Dec 16 '15 at 17:05
  • argh.. i spoke too soon. still get the errors randomly and intermittently. arrrrrrrgh. – patrick Dec 16 '15 at 17:13
  • Maybe those errors because your sprite has no size? No frame? I remember such errors in my old game, just because I added to batchnode nodes and sprites without image([[CCSprite alloc] init]; maybe it's because of that. Try to find what exactly sprite cause this problem. – KAMIKAZE Dec 16 '15 at 19:29
  • its so hard to tell as it's super intermittent. It's like I have to go back and forth between previewing level 1, level 2, over and over and over and over, and then eventually the console will be flooded with those errors, and then they wont happen again after that.. The app doesn't crash, so it's not like its that big of a problem, but I would at least like to know how I can silence openGL errors during that window of time when preloading my levels. – patrick Dec 16 '15 at 19:52
  • Just divide all sprites on the scene, check for errors, remove some sprites, repeat. Until you understand what exactly code produce this errors. – KAMIKAZE Dec 17 '15 at 11:01
4

Can you tell me why you do what you want THAT hard?

If I were you, I would do the following, to load scene async.

  1. Load a middle scene with loading indicator, or add it to the current one. + Create a new scene object.
  2. Dispatch asynchrounously expensive method for that new scene.
  3. After that is done replace the scene with the CCDirector, dispatch tihs operation synchronously

Use GCD as you do right now. You don't really wanna draw your scene while loading it, believe me.

s1ddok
  • 4,615
  • 1
  • 18
  • 31
  • I have a level select screen with a preview window, and selecting a level reveals a spinner in the preview window, that's when the level loads async and the preview window displays the level after it's loaded. The problem is, I just don't understand where these errors are coming from. If I create my new node, and load everything asyc, and then after returning to the main thread, add that new node as a child, there should be no "drawing" until after the node is added as a child. Yet those OpelGL [CCSprite draw] errors still happen. I will try your suggestion and see what happens. – patrick Dec 15 '15 at 18:15