1

I am working on an iOS application that, say on a button click, launches several threads, each executing a piece of Open GL code. These threads either have a different EAGLContext set on them, or if they use same EAGLContext, then they are synchronised (i.e. 2 threads don't set same EAGLContext in parallel).

Now suppose the app goes into background. As per Apple's documentation, we should stop all the OpenGL calls in applicationWillResignActive: callback so that by the time applicationDidEnterBackground: is called, no further GL calls are made.

I am using dispatch_queues to create background threads. For e.g.:

__block Byte* renderedData; // some memory already allocated
dispatch_sync(glProcessingQueue, ^{
  [EAGLContext setCurrentContext:_eaglContext];
  glViewPort(...)
  glBindFramebuffer(...)
  glClear(...)
  glDrawArrays(...)
  glReadPixels(...) // read in renderedData
}
use renderedData for something else

My question is - how to handle applicationWillResignActive: so that any such background GL calls can be not just stopped, but also be able to resume on applicationDidBecomeActive:? Should I wait for currently running blocks to finish before returning from applicationWillResignActive:? Or should I just suspend glProcessingQueue and return?

I have also read that similar is the case when app is interrupted in other ways, like displaying an alert, a phone call, etc.

I can have multiple such threads at any point of time, invoked by possibly multiple ViewControllers, so I am looking for some scalable solution or design pattern.

genpfault
  • 51,148
  • 11
  • 85
  • 139

1 Answers1

0

The way I see it you need to either pause a thread or kill it.

If you kill it you need to ensure all resources are released which means again calling openGL most likely. In this case it might actually be better to simply wait for the block to finish execution. This means the block must not take too long to finish which is impossible to guarantee and since you have multiple contexts and threads this may realistically present an issue.

So pausing seems better. I am not sure if there is a direct API to pause a thread but you can make it wait. Maybe a s system similar to this one can help.

The linked example seems to handle exactly what you would want; it already checks the current thread and locks that one. I guess you could pack that into some tool as a static method or a C function and wherever you are confident you can pause the thread you would simply do something like:

dispatch_sync(glProcessingQueue, ^{
    [EAGLContext setCurrentContext:_eaglContext];
    [ThreadManager pauseCurrentThreadIfNeeded];
    glViewPort(...)
    glBindFramebuffer(...)
    [ThreadManager pauseCurrentThreadIfNeeded];
    glClear(...)
    glDrawArrays(...)
    glReadPixels(...) // read in renderedData
    [ThreadManager pauseCurrentThreadIfNeeded];
}

You might still have an issue with main thread if it is used. You might want to skip pause on that one otherwise your system may simply never wake up again (not sure though, try it).

So now you are look at interface of your ThreadManager to be something like:

+ (void)pause {
    __threadsPaused = YES;
}
+ (void)resume {
    __threadsPaused = NO;
}
+ (void)pauseCurrentThreadIfNeeded {
    if(__threadsPaused) {
        // TODO: insert code for locking until __threadsPaused becomes false
    }
}

Let us know what you find out.

Matic Oblak
  • 16,318
  • 3
  • 24
  • 43
  • Thanks @matic-oblak. As far as I understand then, I need to call `[ThreadManager pause]` from `applicationWillResignActive:`. However, that doesn't guarantee if, by the time `applicationWillResignActive:` will return, all GL threads would have paused. While a good practice, is it enough (or do you want me to find that out)? – ujjaval singh May 31 '18 at 09:37
  • @ujjavalsingh Unless you have an option to pause threads directly on demand and all the processes stop (almost) instantly you will always need a bit time for things to truly stop. But you should have a few seconds window to that in iOS. And even if you stopped all the threads instantly it would not mean the GPU will stop simultaneously.. – Matic Oblak May 31 '18 at 10:12
  • `applicationWillResignActive:` will be called from main thread. So, even if I have the `dispatch_block` currently running, it won't stop before a thread switch, which won't happen till `applicationWillResignActive:` returns. And, as Apple document states, it's sufficient to call `glFlush` to stop GPU. So, my question again: can we say that it's sufficient to suspend such `dispatch_blocks`? – ujjaval singh May 31 '18 at 11:23
  • and if we can't suspend currently running dispatch_block, do we need to then wrap each of its GL calls into some macro like GL_SAFE(code) that first checks if [ThreadManager pauseCurrentThreadIfNeeded] and glFlush, else execute code? – ujjaval singh May 31 '18 at 11:31
  • @ujjavalsingh wow wow hold on there. That is not how it work, none of this does! There is no "thread switch" and yes in most cases those methods will finish first. `glFlush` should flush all operations and should pause current thread until all messages are sent. Execution itself I believe is assured with `glFinish` though. But what the docs expect here obviously is your openGL executing on main thread. This can lead into a very long discussion but a long story short; if you really need to have this then you do need to pause your main thread until all other threads all paused. – Matic Oblak May 31 '18 at 11:36