Releasing GL textures

24 views
Skip to first unread message

Guillermo Rodriguez Garcia

unread,
Mar 20, 2019, 4:59:29 PM3/20/19
to PlayN
Hi,

I am looking at the Texture class and have some questions regarding how to properly dispose them.

Here's the close() method:

  /** Deletes this texture's GPU resources and renders it unusable. */
  @Override public void close () {
    if (!disposed) {
      disposed = true;
      if (gfx.exec().isMainThread()) {
        gfx.gl.glDeleteTexture(id);
      } else {
        gfx.exec().invokeLater(new Runnable() {
          public void run () { gfx.gl.glDeleteTexture(id); }
        });
      }
    }
  }

In my app I am "starting PlayN multiple times" (some backgound here: https://groups.google.com/forum/#!topic/playn/nPQnfKGNEpM -- that thread refers to Android, but I'm doing more or less the same on iOS).
When a GameActivity is destroyed, I am not explicitly disposing my textures, since in theory this was not necessary (right?)

But then, I am wondering what happens in the following situation:

- The user starts a GameActivity
- Some time later, the activity is destroyed and the app returns to the "native" (Android) part
- Some time later, a new GameActivity is started
- At that point, the gc decides to run, collects all unreferenced Texture instances, and calls their finalize() method, which in turn calls close()
- close() calls gfx.gl.glDeleteTexture(id);

May this result in the wrong texture (one with the same id, but related to the "new" GameActivity) being deleted ?

Guillermo

Michael Bayne

unread,
Mar 21, 2019, 12:18:23 PM3/21/19
to pl...@googlegroups.com
I don't think this will happen. Though if you're seeing it happen, maybe there's something I'm missing in my hypothesis.

When a GameActivity goes away, the GameViewGL that's associated with that activity also goes away, and so presumably does the GL context used by that GL game view. So all of the original textures (on the GL side) from the first game activity are destroyed along with the context. The Texture Java objects may linger around as garbage, and when their finalizer is run, they will queue up a runnable to delete the texture but that runnable will never get run, because it's queued up on the old GameActivity "game thread" which was driven by the old GameViewGL "onDrawFrame" callback, which is never going to be called again.

There is the wrinkle of invokeLater calling runOnUiThread if the game is paused, and we can assume the game is paused in this case, but the activity is also presumably destroyed, so I'm not sure what Android does if you call runOnUiThread on a destroyed activity. My guess is that it either throws an exception immediately, or just never runs the callback. In either case, it makes more sense for this callback to be done strictly on the game/GL thread, so I'll change it to use that instead.

But as I say, I don't think this would have been causing any problems.

--

---
You received this message because you are subscribed to the Google Groups "PlayN" group.
To unsubscribe from this group and stop receiving emails from it, send an email to playn+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


--

Guillermo Rodriguez Garcia

unread,
Mar 21, 2019, 12:42:59 PM3/21/19
to pl...@googlegroups.com
Hi Michael,

Thank you for your answer.

El jue., 21 mar. 2019 a las 17:18, Michael Bayne (<m...@samskivert.com>) escribió:
I don't think this will happen. Though if you're seeing it happen, maybe there's something I'm missing in my hypothesis.

I'm not sure if I am seeing it happen.
I am definitely having issues in the iOS version of the app, and these issues are somehow related to GL textures. But I am still not sure what the problem is.
I linked to the previous thread mentioning multiple Android activities just to give some context and not add too much noise. But the problems I am seeing actually happen on the iOS version only.


When a GameActivity goes away, the GameViewGL that's associated with that activity also goes away, and so presumably does the GL context used by that GL game view. So all of the original textures (on the GL side) from the first game activity are destroyed along with the context. The Texture Java objects may linger around as garbage, and when their finalizer is run, they will queue up a runnable to delete the texture but that runnable will never get run, because it's queued up on the old GameActivity "game thread" which was driven by the old GameViewGL "onDrawFrame" callback, which is never going to be called again.

There is the wrinkle of invokeLater calling runOnUiThread if the game is paused, and we can assume the game is paused in this case, but the activity is also presumably destroyed, so I'm not sure what Android does if you call runOnUiThread on a destroyed activity.

Does the above analysis also apply to the iOS backend?
I assume that there may be some differences; in iOS, when the GLKViewController is removed from the view and the game is "paused", invokeLater will end up queuing the operation via NSOperationQueue.getMainQueue(). This is not directly related to the GLKViewController that was just removed, so I assume that the action will actually run...
 
My guess is that it either throws an exception immediately, or just never runs the callback. In either case, it makes more sense for this callback to be done strictly on the game/GL thread, so I'll change it to use that instead.

Won't this break the case when dispose() is called from the finalizer, if there's no game loop anymore?

Guillermo


--
Guillermo Rodriguez Garcia
guille.r...@gmail.com

Michael Bayne

unread,
Mar 21, 2019, 1:01:21 PM3/21/19
to pl...@googlegroups.com
On Thu, Mar 21, 2019 at 9:42 AM Guillermo Rodriguez Garcia <guille.r...@gmail.com> wrote:
I assume that there may be some differences; in iOS, when the GLKViewController is removed from the view and the game is "paused", invokeLater will end up queuing the operation via NSOperationQueue.getMainQueue(). This is not directly related to the GLKViewController that was just removed, so I assume that the action will actually run...

It is possible that the wrong thing could have happened on iOS since as you say the main operation queue is probably still running (assuming your app is still running). So the change I just pushed may in fact fix your problems on iOS.

Won't this break the case when dispose() is called from the finalizer, if there's no game loop anymore?

This should not be a problem for the same reason it's not a problem on Android. If the PlayN instance that managed the game loop has been disposed, then it will have destroyed the GL context that it was using to render, and when the GL context is destroyed, all textures are destroyed as well. So the individual Texture Java objects can just be collected at that point. They of course do not know that this happened, so they will need to still queue up a request to destroy their texture, but it can be safely dropped.

There appears to be no way to explicitly destroy or dispose of an EAGLContext on iOS, but I assume that when the RoboViewController and the GLKView that created and were using the context eventually go away, that the EAGLContext will be garbage collected itself and destroyed, and all associate GPU resources (like textures) will be cleaned up.

Guillermo Rodriguez Garcia

unread,
Mar 21, 2019, 1:40:36 PM3/21/19
to pl...@googlegroups.com
El jue., 21 mar. 2019 a las 18:01, Michael Bayne (<m...@samskivert.com>) escribió:
On Thu, Mar 21, 2019 at 9:42 AM Guillermo Rodriguez Garcia <guille.r...@gmail.com> wrote:
I assume that there may be some differences; in iOS, when the GLKViewController is removed from the view and the game is "paused", invokeLater will end up queuing the operation via NSOperationQueue.getMainQueue(). This is not directly related to the GLKViewController that was just removed, so I assume that the action will actually run...

It is possible that the wrong thing could have happened on iOS since as you say the main operation queue is probably still running (assuming your app is still running). So the change I just pushed may in fact fix your problems on iOS.

Actually, yes, it looks like this fixed the problem with textures :-)
The symptoms were (again) black or wrong textures, if the PlayN app was started multiple times. I made sure I was not reusing any icons or resources between runs.
So I guess that what was happening was essentially this:

- The user starts a session (a RoboViewController is created and pushed onto the screen)
- Some time later, the RoboViewController is dismissed and the app returns to the "native" (iOS) part
- Some time later, a new session (new RoboViewController) is started
- At that point, the gc decides to run, collects all unreferenced Texture instances, and calls their finalize() method, which in turn calls close()
- close() enqueues a call to gfx.gl.glDeleteTexture(id) on the main operation queue. This runs on the main thread, and deletes the "wrong" texture (a texture with the same id, but belonging to the "new" RoboViewController)

Now I don't see this happening anymore. I still have to do a bit more testing but this looks promising :-))

Won't this break the case when dispose() is called from the finalizer, if there's no game loop anymore?

This should not be a problem for the same reason it's not a problem on Android. If the PlayN instance that managed the game loop has been disposed, then it will have destroyed the GL context that it was using to render, and when the GL context is destroyed, all textures are destroyed as well. So the individual Texture Java objects can just be collected at that point. They of course do not know that this happened, so they will need to still queue up a request to destroy their texture, but it can be safely dropped.

There appears to be no way to explicitly destroy or dispose of an EAGLContext on iOS, but I assume that when the RoboViewController and the GLKView that created and were using the context eventually go away, that the EAGLContext will be garbage collected itself and destroyed, and all associate GPU resources (like textures) will be cleaned up.

Unfortunately there are issues here as well.
Even though some leaks were already fixed in RoboViewController and its GLKView, there must be some others. What I am seeing right now is that the RoboPlatform instances are never GC'ed and thus there's a bunch of associated stuff that is never collected either.

Need to dig further into that.

Thank you!

Guillermo
Reply all
Reply to author
Forward
0 new messages